mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Update for 6.2 without rework.
This commit is contained in:
parent
a36d1f1935
commit
8de1f56766
21 changed files with 902 additions and 169 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@ public class CharaMakeParams : ExcelRow
|
||||||
|
|
||||||
public sbyte Gender { get; set; }
|
public sbyte Gender { get; set; }
|
||||||
|
|
||||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
||||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
public byte[] Voices { get; set; } = new byte[NumVoices];
|
||||||
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
||||||
public CharaMakeType.UnkData3347Obj[] Equip { get; set; } = new CharaMakeType.UnkData3347Obj[NumEquip];
|
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
||||||
|
|
||||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
||||||
{
|
{
|
||||||
|
|
@ -103,7 +103,7 @@ public class CharaMakeParams : ExcelRow
|
||||||
|
|
||||||
for (var i = 0; i < NumEquip; ++i)
|
for (var i = 0; i < NumEquip; ++i)
|
||||||
{
|
{
|
||||||
Equip[i] = new CharaMakeType.UnkData3347Obj()
|
Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj()
|
||||||
{
|
{
|
||||||
Helmet = parser.ReadColumn<ulong>(
|
Helmet = parser.ReadColumn<ulong>(
|
||||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
|
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Glamourer.Customization
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct CharacterCustomization
|
public struct CharacterCustomization
|
||||||
{
|
{
|
||||||
public const int CustomizationOffset = 0x830;
|
public const int CustomizationOffset = 0x840;
|
||||||
public const int CustomizationBytes = 26;
|
public const int CustomizationBytes = 26;
|
||||||
|
|
||||||
public static CharacterCustomization Default = new()
|
public static CharacterCustomization Default = new()
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Logging;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
@ -76,7 +77,6 @@ namespace Glamourer
|
||||||
var slot = (EquipSlot) item.EquipSlotCategory.Row;
|
var slot = (EquipSlot) item.EquipSlotCategory.Row;
|
||||||
if (slot == EquipSlot.Unknown)
|
if (slot == EquipSlot.Unknown)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
slot = slot.ToSlot();
|
slot = slot.ToSlot();
|
||||||
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,72 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<RootNamespace>Glamourer</RootNamespace>
|
<RootNamespace>Glamourer</RootNamespace>
|
||||||
<AssemblyName>Glamourer.GameData</AssemblyName>
|
<AssemblyName>Glamourer.GameData</AssemblyName>
|
||||||
<FileVersion>1.0.0.0</FileVersion>
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
<Company>SoftOtter</Company>
|
<Company>SoftOtter</Company>
|
||||||
<Product>Glamourer</Product>
|
<Product>Glamourer</Product>
|
||||||
<Copyright>Copyright © 2020</Copyright>
|
<Copyright>Copyright © 2020</Copyright>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||||
</PropertyGroup>
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<PropertyGroup>
|
||||||
<Reference Include="Dalamud">
|
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>
|
</PropertyGroup>
|
||||||
<HintPath>..\libs\Dalamud.dll</HintPath>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ImGuiScene">
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina">
|
|
||||||
<HintPath>$(DALAMUD_ROOT)\Lumina.dll</HintPath>
|
|
||||||
<HintPath>..\libs\Lumina.dll</HintPath>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina.Excel">
|
|
||||||
<HintPath>$(DALAMUD_ROOT)\Lumina.Excel.dll</HintPath>
|
|
||||||
<HintPath>..\libs\Lumina.Excel.dll</HintPath>
|
|
||||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<Reference Include="Dalamud">
|
||||||
</ItemGroup>
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
</Project>
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGuiScene">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
|
<ProjectReference Include="..\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamo
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{9BEE2336-AA93-4669-8EEA-4756B3B2D024}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{9BEE2336-AA93-4669-8EEA-4756B3B2D024}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
|
||||||
BIN
Glamourer.zip
BIN
Glamourer.zip
Binary file not shown.
|
|
@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Logging;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Api;
|
using Glamourer.Api;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
|
|
|
||||||
|
|
@ -1,113 +1,124 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<RootNamespace>Glamourer</RootNamespace>
|
<RootNamespace>Glamourer</RootNamespace>
|
||||||
<AssemblyName>Glamourer</AssemblyName>
|
<AssemblyName>Glamourer</AssemblyName>
|
||||||
<FileVersion>0.1.0.5</FileVersion>
|
<FileVersion>0.1.1.0</FileVersion>
|
||||||
<AssemblyVersion>0.1.0.5</AssemblyVersion>
|
<AssemblyVersion>0.1.1.0</AssemblyVersion>
|
||||||
<Company>SoftOtter</Company>
|
<Company>SoftOtter</Company>
|
||||||
<Product>Glamourer</Product>
|
<Product>Glamourer</Product>
|
||||||
<Copyright>Copyright © 2020</Copyright>
|
<Copyright>Copyright © 2020</Copyright>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
<DebugType>full</DebugType>
|
<DebugType>full</DebugType>
|
||||||
<Optimize>false</Optimize>
|
<Optimize>false</Optimize>
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
<Optimize>true</Optimize>
|
<Optimize>true</Optimize>
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="LegacyTattoo.raw" />
|
<None Remove="LegacyTattoo.raw" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Include="LegacyTattoo.raw" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Dalamud">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ImGui.NET">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ImGuiScene">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="SDL2-CS">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\SDL2-CS.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina.Excel">
|
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3">
|
<EmbeddedResource Include="LegacyTattoo.raw" />
|
||||||
<Private>false</Private>
|
</ItemGroup>
|
||||||
</PackageReference>
|
|
||||||
|
|
||||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
<PropertyGroup>
|
||||||
</ItemGroup>
|
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
<Reference Include="Dalamud">
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj" />
|
<Private>False</Private>
|
||||||
</ItemGroup>
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGui.NET">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGuiScene">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Properties\Resources.Designer.cs">
|
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||||
<DesignTime>True</DesignTime>
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
<AutoGen>True</AutoGen>
|
</ItemGroup>
|
||||||
<DependentUpon>Resources.resx</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Properties\Resources.resx">
|
<Compile Update="Properties\Resources.Designer.cs">
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<DesignTime>True</DesignTime>
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<AutoGen>True</AutoGen>
|
||||||
</EmbeddedResource>
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
</ItemGroup>
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="Glamourer.json">
|
<EmbeddedResource Update="Properties\Resources.resx">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
</None>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</ItemGroup>
|
</EmbeddedResource>
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
</ItemGroup>
|
||||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.PlayerWatch.dll $(SolutionDir)$(SolutionName).zip" />
|
|
||||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||||
</Target>
|
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
|
||||||
|
</Exec>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Glamourer.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll $(SolutionDir)$(SolutionName).zip" />
|
||||||
|
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
||||||
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
||||||
"InternalName": "Glamourer",
|
"InternalName": "Glamourer",
|
||||||
"AssemblyVersion": "0.1.0.5",
|
"AssemblyVersion": "0.1.1.0",
|
||||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 6,
|
"DalamudApiLevel": 7,
|
||||||
"ImageUrls": null,
|
"ImageUrls": null,
|
||||||
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -47,7 +45,7 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
_stains = GameData.Stains(Dalamud.GameData);
|
_stains = GameData.Stains(Dalamud.GameData);
|
||||||
_models = GameData.Models(Dalamud.GameData);
|
_models = GameData.Models(Dalamud.GameData);
|
||||||
_identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
_identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData);
|
||||||
|
|
||||||
|
|
||||||
var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
|
var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using ImGuiNET;
|
||||||
using Lumina.Text;
|
using Lumina.Text;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@ public static class Offsets
|
||||||
{
|
{
|
||||||
public static class Character
|
public static class Character
|
||||||
{
|
{
|
||||||
public const int Wetness = 0x1ADA;
|
public const int Wetness = 0x1AF3;
|
||||||
public const int HatVisible = 0x84E;
|
public const int HatVisible = 0x85E;
|
||||||
public const int VisorToggled = 0x84F;
|
public const int VisorToggled = 0x85F;
|
||||||
public const int WeaponHidden1 = 0x84F;
|
public const int WeaponHidden1 = 0x85F;
|
||||||
public const int WeaponHidden2 = 0x72C;
|
public const int WeaponHidden2 = 0x73C;
|
||||||
public const int Alpha = 0x19E0;
|
public const int Alpha = 0x19F8;
|
||||||
|
|
||||||
public static class Flags
|
public static class Flags
|
||||||
{
|
{
|
||||||
public const byte IsHatHidden = 0x01;
|
public const byte IsHatHidden = 0x01;
|
||||||
public const byte IsVisorToggled = 0x08;
|
public const byte IsVisorToggled = 0x08;
|
||||||
public const byte IsWet = 0x80;
|
public const byte IsWet = 0x40;
|
||||||
public const byte IsWeaponHidden1 = 0x01;
|
public const byte IsWeaponHidden1 = 0x01;
|
||||||
public const byte IsWeaponHidden2 = 0x02;
|
public const byte IsWeaponHidden2 = 0x02;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
145
Penumbra.PlayerWatch/CharacterEquipment.cs
Normal file
145
Penumbra.PlayerWatch/CharacterEquipment.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
// Read the customization data regarding weapons and displayable equipment from an actor struct.
|
||||||
|
// Stores the data in a 56 bytes, i.e. 7 longs for easier comparison.
|
||||||
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public class CharacterEquipment
|
||||||
|
{
|
||||||
|
public const int MainWeaponOffset = 0x6E0;
|
||||||
|
public const int OffWeaponOffset = 0x748;
|
||||||
|
public const int EquipmentOffset = 0x818;
|
||||||
|
public const int EquipmentSlots = 10;
|
||||||
|
public const int WeaponSlots = 2;
|
||||||
|
|
||||||
|
public CharacterWeapon MainHand;
|
||||||
|
public CharacterWeapon OffHand;
|
||||||
|
public CharacterArmor Head;
|
||||||
|
public CharacterArmor Body;
|
||||||
|
public CharacterArmor Hands;
|
||||||
|
public CharacterArmor Legs;
|
||||||
|
public CharacterArmor Feet;
|
||||||
|
public CharacterArmor Ears;
|
||||||
|
public CharacterArmor Neck;
|
||||||
|
public CharacterArmor Wrists;
|
||||||
|
public CharacterArmor RFinger;
|
||||||
|
public CharacterArmor LFinger;
|
||||||
|
public ushort IsSet; // Also fills struct size to 56, a multiple of 8.
|
||||||
|
|
||||||
|
public CharacterEquipment()
|
||||||
|
=> Clear();
|
||||||
|
|
||||||
|
public CharacterEquipment(Character actor)
|
||||||
|
: this(actor.Address)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> IsSet == 0
|
||||||
|
? "(Not Set)"
|
||||||
|
: $"({MainHand}) | ({OffHand}) | ({Head}) | ({Body}) | ({Hands}) | ({Legs}) | "
|
||||||
|
+ $"({Feet}) | ({Ears}) | ({Neck}) | ({Wrists}) | ({LFinger}) | ({RFinger})";
|
||||||
|
|
||||||
|
public bool Equal(Character rhs)
|
||||||
|
=> CompareData(new CharacterEquipment(rhs));
|
||||||
|
|
||||||
|
public bool Equal(CharacterEquipment rhs)
|
||||||
|
=> CompareData(rhs);
|
||||||
|
|
||||||
|
public bool CompareAndUpdate(Character rhs)
|
||||||
|
=> CompareAndOverwrite(new CharacterEquipment(rhs));
|
||||||
|
|
||||||
|
public bool CompareAndUpdate(CharacterEquipment rhs)
|
||||||
|
=> CompareAndOverwrite(rhs);
|
||||||
|
|
||||||
|
private unsafe CharacterEquipment(IntPtr actorAddress)
|
||||||
|
{
|
||||||
|
IsSet = 1;
|
||||||
|
var actorPtr = (byte*)actorAddress.ToPointer();
|
||||||
|
fixed (CharacterWeapon* main = &MainHand, off = &OffHand)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(actorPtr + MainWeaponOffset, main, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
|
||||||
|
Buffer.MemoryCopy(actorPtr + OffWeaponOffset, off, sizeof(CharacterWeapon), sizeof(CharacterWeapon));
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (CharacterArmor* equipment = &Head)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(actorPtr + EquipmentOffset, equipment, EquipmentSlots * sizeof(CharacterArmor),
|
||||||
|
EquipmentSlots * sizeof(CharacterArmor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Clear()
|
||||||
|
{
|
||||||
|
fixed (CharacterWeapon* main = &MainHand)
|
||||||
|
{
|
||||||
|
var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8;
|
||||||
|
for (ulong* ptr = (ulong*)main, end = ptr + structSizeEights; ptr != end; ++ptr)
|
||||||
|
*ptr = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool CompareAndOverwrite(CharacterEquipment rhs)
|
||||||
|
{
|
||||||
|
var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8;
|
||||||
|
var ret = true;
|
||||||
|
fixed (CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand)
|
||||||
|
{
|
||||||
|
var ptr1 = (ulong*)data1;
|
||||||
|
var ptr2 = (ulong*)data2;
|
||||||
|
for (var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2)
|
||||||
|
{
|
||||||
|
if (*ptr1 != *ptr2)
|
||||||
|
{
|
||||||
|
*ptr1 = *ptr2;
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool CompareData(CharacterEquipment rhs)
|
||||||
|
{
|
||||||
|
var structSizeEights = (2 + EquipmentSlots * sizeof(CharacterArmor) + WeaponSlots * sizeof(CharacterWeapon)) / 8;
|
||||||
|
fixed (CharacterWeapon* data1 = &MainHand, data2 = &rhs.MainHand)
|
||||||
|
{
|
||||||
|
var ptr1 = (ulong*)data1;
|
||||||
|
var ptr2 = (ulong*)data2;
|
||||||
|
for (var end = ptr1 + structSizeEights; ptr1 != end; ++ptr1, ++ptr2)
|
||||||
|
{
|
||||||
|
if (*ptr1 != *ptr2)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||||
|
{
|
||||||
|
fixed (CharacterWeapon* data = &MainHand)
|
||||||
|
{
|
||||||
|
Marshal.Copy(new IntPtr(data), array, offset, 56);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var ret = new byte[56];
|
||||||
|
WriteBytes(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void FromBytes(byte[] array, int offset = 0)
|
||||||
|
{
|
||||||
|
fixed (CharacterWeapon* data = &MainHand)
|
||||||
|
{
|
||||||
|
Marshal.Copy(array, offset, new IntPtr(data), 56);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Penumbra.PlayerWatch/CharacterFactory.cs
Normal file
63
Penumbra.PlayerWatch/CharacterFactory.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
|
||||||
|
namespace Penumbra.PlayerWatch
|
||||||
|
{
|
||||||
|
public static class CharacterFactory
|
||||||
|
{
|
||||||
|
private static ConstructorInfo? _characterConstructor;
|
||||||
|
|
||||||
|
private static void Initialize()
|
||||||
|
{
|
||||||
|
_characterConstructor ??= typeof( Character ).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[]
|
||||||
|
{
|
||||||
|
typeof( IntPtr ),
|
||||||
|
}, null )!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Character Character( IntPtr address )
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
return ( Character )_characterConstructor?.Invoke( new object[]
|
||||||
|
{
|
||||||
|
address,
|
||||||
|
} )!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Character? Convert( GameObject? actor )
|
||||||
|
{
|
||||||
|
if( actor == null )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actor switch
|
||||||
|
{
|
||||||
|
PlayerCharacter p => p,
|
||||||
|
BattleChara b => b,
|
||||||
|
_ => actor.ObjectKind switch
|
||||||
|
{
|
||||||
|
ObjectKind.BattleNpc => Character( actor.Address ),
|
||||||
|
ObjectKind.Companion => Character( actor.Address ),
|
||||||
|
ObjectKind.Retainer => Character( actor.Address ),
|
||||||
|
ObjectKind.EventNpc => Character( actor.Address ),
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GameObjectExtensions
|
||||||
|
{
|
||||||
|
private const int ModelTypeOffset = 0x01B4;
|
||||||
|
|
||||||
|
public static unsafe int ModelType( this GameObject actor )
|
||||||
|
=> *( int* )( actor.Address + ModelTypeOffset );
|
||||||
|
|
||||||
|
public static unsafe void SetModelType( this GameObject actor, int value )
|
||||||
|
=> *( int* )( actor.Address + ModelTypeOffset ) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Penumbra.PlayerWatch/IPlayerWatcher.cs
Normal file
30
Penumbra.PlayerWatch/IPlayerWatcher.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
public delegate void PlayerChange( Character actor );
|
||||||
|
|
||||||
|
public interface IPlayerWatcherBase : IDisposable
|
||||||
|
{
|
||||||
|
public int Version { get; }
|
||||||
|
public bool Valid { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPlayerWatcher : IPlayerWatcherBase
|
||||||
|
{
|
||||||
|
public event PlayerChange? PlayerChanged;
|
||||||
|
public bool Active { get; }
|
||||||
|
|
||||||
|
public void Enable();
|
||||||
|
public void Disable();
|
||||||
|
public void SetStatus( bool enabled );
|
||||||
|
|
||||||
|
public void AddPlayerToWatch( string playerName );
|
||||||
|
public void RemovePlayerFromWatch( string playerName );
|
||||||
|
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor );
|
||||||
|
|
||||||
|
public IEnumerable< (string, (ulong, CharacterEquipment)[]) > WatchedPlayers();
|
||||||
|
}
|
||||||
46
Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj
Normal file
46
Penumbra.PlayerWatch/Penumbra.PlayerWatch.csproj
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
|
<LangVersion>preview</LangVersion>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<AssemblyTitle>Penumbra.PlayerWatch</AssemblyTitle>
|
||||||
|
<Company>absolute gangstas</Company>
|
||||||
|
<Product>Penumbra</Product>
|
||||||
|
<Copyright>Copyright © 2020</Copyright>
|
||||||
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
323
Penumbra.PlayerWatch/PlayerWatchBase.cs
Normal file
323
Penumbra.PlayerWatch/PlayerWatchBase.cs
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
internal readonly struct WatchedPlayer
|
||||||
|
{
|
||||||
|
public readonly Dictionary< ulong, CharacterEquipment > FoundActors;
|
||||||
|
public readonly HashSet< PlayerWatcher > RegisteredWatchers;
|
||||||
|
|
||||||
|
public WatchedPlayer( PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
FoundActors = new Dictionary< ulong, CharacterEquipment >( 4 );
|
||||||
|
RegisteredWatchers = new HashSet< PlayerWatcher > { watcher };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class PlayerWatchBase : IDisposable
|
||||||
|
{
|
||||||
|
public const int GPosePlayerIdx = 201;
|
||||||
|
public const int GPoseTableEnd = GPosePlayerIdx + 40;
|
||||||
|
private const int ObjectsPerFrame = 32;
|
||||||
|
|
||||||
|
private readonly Framework _framework;
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
private readonly ObjectTable _objects;
|
||||||
|
internal readonly HashSet< PlayerWatcher > RegisteredWatchers = new();
|
||||||
|
internal readonly Dictionary< string, WatchedPlayer > Equip = new();
|
||||||
|
internal HashSet< ulong > SeenActors;
|
||||||
|
private int _frameTicker;
|
||||||
|
private bool _inGPose;
|
||||||
|
private bool _enabled;
|
||||||
|
private bool _cancel;
|
||||||
|
|
||||||
|
internal PlayerWatchBase( Framework framework, ClientState clientState, ObjectTable objects )
|
||||||
|
{
|
||||||
|
_framework = framework;
|
||||||
|
_clientState = clientState;
|
||||||
|
_objects = objects;
|
||||||
|
SeenActors = new HashSet< ulong >( _objects.Length );
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RegisterWatcher( PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
RegisteredWatchers.Add( watcher );
|
||||||
|
if( watcher.Active )
|
||||||
|
{
|
||||||
|
EnablePlayerWatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void UnregisterWatcher( PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
if( RegisteredWatchers.Remove( watcher ) )
|
||||||
|
{
|
||||||
|
foreach( var (key, value) in Equip.ToArray() )
|
||||||
|
{
|
||||||
|
if( value.RegisteredWatchers.Remove( watcher ) && value.RegisteredWatchers.Count == 0 )
|
||||||
|
{
|
||||||
|
Equip.Remove( key );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckActiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CheckActiveStatus()
|
||||||
|
{
|
||||||
|
if( RegisteredWatchers.Any( w => w.Active ) )
|
||||||
|
{
|
||||||
|
EnablePlayerWatch();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisablePlayerWatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong GetId( GameObject actor )
|
||||||
|
=> actor.ObjectId | ( ( ulong )actor.OwnerId << 32 );
|
||||||
|
|
||||||
|
internal CharacterEquipment UpdatePlayerWithoutEvent( Character actor )
|
||||||
|
{
|
||||||
|
var name = actor.Name.ToString();
|
||||||
|
var equipment = new CharacterEquipment( actor );
|
||||||
|
if( Equip.TryGetValue( name, out var watched ) )
|
||||||
|
{
|
||||||
|
watched.FoundActors[ GetId( actor ) ] = equipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return equipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddPlayerToWatch( string playerName, PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
if( Equip.TryGetValue( playerName, out var items ) )
|
||||||
|
{
|
||||||
|
items.RegisteredWatchers.Add( watcher );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Equip[ playerName ] = new WatchedPlayer( watcher );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlayerFromWatch( string playerName, PlayerWatcher watcher )
|
||||||
|
{
|
||||||
|
if( Equip.TryGetValue( playerName, out var items ) )
|
||||||
|
{
|
||||||
|
if( items.RegisteredWatchers.Remove( watcher ) && items.RegisteredWatchers.Count == 0 )
|
||||||
|
{
|
||||||
|
Equip.Remove( playerName );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EnablePlayerWatch()
|
||||||
|
{
|
||||||
|
if( !_enabled )
|
||||||
|
{
|
||||||
|
_enabled = true;
|
||||||
|
_framework.Update += OnFrameworkUpdate;
|
||||||
|
_clientState.TerritoryChanged += OnTerritoryChange;
|
||||||
|
_clientState.Logout += OnLogout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DisablePlayerWatch()
|
||||||
|
{
|
||||||
|
if( _enabled )
|
||||||
|
{
|
||||||
|
_enabled = false;
|
||||||
|
_framework.Update -= OnFrameworkUpdate;
|
||||||
|
_clientState.TerritoryChanged -= OnTerritoryChange;
|
||||||
|
_clientState.Logout -= OnLogout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> DisablePlayerWatch();
|
||||||
|
|
||||||
|
private void OnTerritoryChange( object? _1, ushort _2 )
|
||||||
|
=> Clear();
|
||||||
|
|
||||||
|
private void OnLogout( object? _1, object? _2 )
|
||||||
|
=> Clear();
|
||||||
|
|
||||||
|
internal void Clear()
|
||||||
|
{
|
||||||
|
PluginLog.Debug( "Clearing PlayerWatcher Store." );
|
||||||
|
_cancel = true;
|
||||||
|
foreach( var kvp in Equip )
|
||||||
|
{
|
||||||
|
kvp.Value.FoundActors.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_frameTicker = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TriggerEvents( IEnumerable< PlayerWatcher > watchers, Character player )
|
||||||
|
{
|
||||||
|
PluginLog.Debug( "Triggering events for {PlayerName} at {Address}.", player.Name, player.Address );
|
||||||
|
foreach( var watcher in watchers.Where( w => w.Active ) )
|
||||||
|
{
|
||||||
|
watcher.Trigger( player );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TriggerGPose()
|
||||||
|
{
|
||||||
|
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
||||||
|
{
|
||||||
|
var player = _objects[ i ];
|
||||||
|
if( player == null )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( Equip.TryGetValue( player.Name.ToString(), out var watcher ) )
|
||||||
|
{
|
||||||
|
TriggerEvents( watcher.RegisteredWatchers, ( Character )player );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Character? CheckGPoseObject( GameObject player )
|
||||||
|
{
|
||||||
|
if( !_inGPose )
|
||||||
|
{
|
||||||
|
return CharacterFactory.Convert( player );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( var i = GPosePlayerIdx; i < GPoseTableEnd; ++i )
|
||||||
|
{
|
||||||
|
var a = _objects[ i ];
|
||||||
|
if( a == null )
|
||||||
|
{
|
||||||
|
return CharacterFactory.Convert( player );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( a.Name == player.Name )
|
||||||
|
{
|
||||||
|
return CharacterFactory.Convert( a );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CharacterFactory.Convert( player )!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetPlayer( GameObject gameObject, out WatchedPlayer watch )
|
||||||
|
{
|
||||||
|
watch = default;
|
||||||
|
var name = gameObject.Name.ToString();
|
||||||
|
return name.Length != 0 && Equip.TryGetValue( name, out watch );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InvalidObjectKind( ObjectKind kind )
|
||||||
|
{
|
||||||
|
return kind switch
|
||||||
|
{
|
||||||
|
ObjectKind.BattleNpc => false,
|
||||||
|
ObjectKind.EventNpc => false,
|
||||||
|
ObjectKind.Player => false,
|
||||||
|
ObjectKind.Retainer => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject? GetNextObject()
|
||||||
|
{
|
||||||
|
if( _frameTicker == GPosePlayerIdx - 1 )
|
||||||
|
{
|
||||||
|
_frameTicker = GPoseTableEnd;
|
||||||
|
}
|
||||||
|
else if( _frameTicker == _objects.Length - 1 )
|
||||||
|
{
|
||||||
|
_frameTicker = 0;
|
||||||
|
foreach( var (_, equip) in Equip.Values.SelectMany( d => d.FoundActors.Where( p => !SeenActors.Contains( p.Key ) ) ) )
|
||||||
|
{
|
||||||
|
equip.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SeenActors.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++_frameTicker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _objects[ _frameTicker ];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnFrameworkUpdate( object framework )
|
||||||
|
{
|
||||||
|
var newInGPose = _objects[ GPosePlayerIdx ] != null;
|
||||||
|
|
||||||
|
if( newInGPose != _inGPose )
|
||||||
|
{
|
||||||
|
if( newInGPose )
|
||||||
|
{
|
||||||
|
TriggerGPose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
_inGPose = newInGPose;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( var i = 0; i < ObjectsPerFrame; ++i )
|
||||||
|
{
|
||||||
|
var actor = GetNextObject();
|
||||||
|
if( actor == null
|
||||||
|
|| InvalidObjectKind( actor.ObjectKind )
|
||||||
|
|| !TryGetPlayer( actor, out var watch ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var character = CheckGPoseObject( actor );
|
||||||
|
if( _cancel )
|
||||||
|
{
|
||||||
|
_cancel = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( character == null || character.ModelType() != 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = GetId( character );
|
||||||
|
SeenActors.Add( id );
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
PluginLog.Verbose( "Comparing Gear for {PlayerName:l} ({Id}) at 0x{Address:X}...", character.Name, id, character.Address.ToInt64() );
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if( !watch.FoundActors.TryGetValue( id, out var equip ) )
|
||||||
|
{
|
||||||
|
equip = new CharacterEquipment( character );
|
||||||
|
watch.FoundActors[ id ] = equip;
|
||||||
|
TriggerEvents( watch.RegisteredWatchers, character );
|
||||||
|
}
|
||||||
|
else if( !equip.CompareAndUpdate( character ) )
|
||||||
|
{
|
||||||
|
TriggerEvents( watch.RegisteredWatchers, character );
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // Only one comparison per frame.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Penumbra.PlayerWatch/PlayerWatcher.cs
Normal file
103
Penumbra.PlayerWatch/PlayerWatcher.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
public class PlayerWatcher : IPlayerWatcher
|
||||||
|
{
|
||||||
|
public int Version
|
||||||
|
=> 3;
|
||||||
|
|
||||||
|
private static PlayerWatchBase? _playerWatch;
|
||||||
|
|
||||||
|
public event PlayerChange? PlayerChanged;
|
||||||
|
|
||||||
|
public bool Active { get; set; } = true;
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> _playerWatch != null;
|
||||||
|
|
||||||
|
internal PlayerWatcher( Framework framework, ClientState clientState, ObjectTable objects )
|
||||||
|
{
|
||||||
|
_playerWatch ??= new PlayerWatchBase( framework, clientState, objects );
|
||||||
|
_playerWatch.RegisterWatcher( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enable()
|
||||||
|
=> SetStatus( true );
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
=> SetStatus( false );
|
||||||
|
|
||||||
|
public void SetStatus( bool enabled )
|
||||||
|
{
|
||||||
|
Active = enabled && Valid;
|
||||||
|
_playerWatch?.CheckActiveStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Trigger( Character actor )
|
||||||
|
=> PlayerChanged?.Invoke( actor );
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if( _playerWatch == null )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Active = false;
|
||||||
|
PlayerChanged = null;
|
||||||
|
_playerWatch.UnregisterWatcher( this );
|
||||||
|
if( _playerWatch.RegisteredWatchers.Count == 0 )
|
||||||
|
{
|
||||||
|
_playerWatch.Dispose();
|
||||||
|
_playerWatch = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckValidity()
|
||||||
|
{
|
||||||
|
if( !Valid )
|
||||||
|
{
|
||||||
|
throw new Exception( $"PlayerWatch was already disposed." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPlayerToWatch( string name )
|
||||||
|
{
|
||||||
|
CheckValidity();
|
||||||
|
_playerWatch!.AddPlayerToWatch( name, this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlayerFromWatch( string playerName )
|
||||||
|
{
|
||||||
|
CheckValidity();
|
||||||
|
_playerWatch!.RemovePlayerFromWatch( playerName, this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharacterEquipment UpdatePlayerWithoutEvent( Character actor )
|
||||||
|
{
|
||||||
|
CheckValidity();
|
||||||
|
return _playerWatch!.UpdatePlayerWithoutEvent( actor );
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable< (string, (ulong, CharacterEquipment)[]) > WatchedPlayers()
|
||||||
|
{
|
||||||
|
CheckValidity();
|
||||||
|
return _playerWatch!.Equip
|
||||||
|
.Where( kvp => kvp.Value.RegisteredWatchers.Contains( this ) )
|
||||||
|
.Select( kvp => ( kvp.Key, kvp.Value.FoundActors.Select( kvp2 => ( kvp2.Key, kvp2.Value ) ).ToArray() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlayerWatchFactory
|
||||||
|
{
|
||||||
|
public static IPlayerWatcher Create( Framework framework, ClientState clientState, ObjectTable objects )
|
||||||
|
=> new PlayerWatcher( framework, clientState, objects );
|
||||||
|
}
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
||||||
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
||||||
"InternalName": "Glamourer",
|
"InternalName": "Glamourer",
|
||||||
"AssemblyVersion": "0.1.0.5",
|
"AssemblyVersion": "0.1.1.0",
|
||||||
"TestingAssemblyVersion": "0.1.0.5",
|
"TestingAssemblyVersion": "0.1.1.0",
|
||||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 6,
|
"DalamudApiLevel": 7,
|
||||||
"IsHide": "False",
|
"IsHide": "False",
|
||||||
"IsTestingExclusive": "false",
|
"IsTestingExclusive": "false",
|
||||||
"DownloadCount": 1,
|
"DownloadCount": 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue