refactor: clean up old code

This commit is contained in:
Anna 2020-12-29 11:08:21 -05:00
parent f74b81dc20
commit 5935df195d
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
21 changed files with 759 additions and 3976 deletions

View File

@ -1,203 +0,0 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.{cs,json}]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = lf
insert_final_newline = true
[*.cs]
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = true:silent
dotnet_style_qualification_for_field = true:silent
dotnet_style_qualification_for_method = true:silent
dotnet_style_qualification_for_property = true:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:suggestion
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false:silent
csharp_style_var_for_built_in_types = false:silent
csharp_style_var_when_type_is_apparent = false:silent
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
# Code-block preferences
csharp_prefer_braces = true:error
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = false
csharp_new_line_before_else = false
csharp_new_line_before_finally = false
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = none
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

View File

@ -6,9 +6,6 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peeping Tom", "Peeping Tom\Peeping Tom.csproj", "{888F98DF-AF1D-4852-8411-11B1FEEFE674}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B879446-A687-4B9D-8628-807CCB8C51AE}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -5,20 +5,17 @@ using System.Numerics;
namespace PeepingTom {
[Serializable]
class Configuration : IPluginConfiguration {
internal class Configuration : IPluginConfiguration {
public int Version { get; set; } = 1;
[NonSerialized]
private DalamudPluginInterface pi;
private DalamudPluginInterface Interface { get; set; } = null!;
public bool MarkTargeted { get; set; } = false;
public bool MarkTargeted { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2235:Mark all non-serializable fields", Justification = "it works?")]
public Vector4 TargetedColour { get; set; } = new Vector4(0f, 1f, 0f, 1f);
public float TargetedSize { get; set; } = 2f;
public bool MarkTargeting { get; set; } = false;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2235:Mark all non-serializable fields", Justification = "it works?")]
public bool MarkTargeting { get; set; }
public Vector4 TargetingColour { get; set; } = new Vector4(1f, 0f, 0f, 1f);
public float TargetingSize { get; set; } = 2f;
@ -29,35 +26,35 @@ namespace PeepingTom {
public int NumHistory { get; set; } = 5;
public bool LogParty { get; set; } = true;
public bool LogAlliance { get; set; } = false;
public bool LogInCombat { get; set; } = false;
public bool LogSelf { get; set; } = false;
public bool LogAlliance { get; set; }
public bool LogInCombat { get; set; }
public bool LogSelf { get; set; }
public bool FocusTargetOnHover { get; set; } = true;
public bool OpenExamine { get; set; } = false;
public bool PlaySoundOnTarget { get; set; } = false;
public string SoundPath { get; set; } = null;
public bool PlaySoundOnTarget { get; set; }
public string? SoundPath { get; set; }
public float SoundVolume { get; set; } = 1f;
public int SoundDevice { get; set; } = -1;
public float SoundCooldown { get; set; } = 10f;
public bool PlaySoundWhenClosed { get; set; } = false;
public bool PlaySoundWhenClosed { get; set; }
public bool OpenOnLogin { get; set; } = false;
public bool OpenOnLogin { get; set; }
public bool AllowMovement { get; set; } = true;
public bool AllowResize { get; set; } = true;
public bool ShowInCombat { get; set; } = false;
public bool ShowInInstance { get; set; } = false;
public bool ShowInCutscenes { get; set; } = false;
public bool ShowInCombat { get; set; }
public bool ShowInInstance { get; set; }
public bool ShowInCutscenes { get; set; }
public int PollFrequency { get; set; } = 100;
public void Initialize(DalamudPluginInterface pluginInterface) {
this.pi = pluginInterface;
this.Interface = pluginInterface;
}
public void Save() {
this.pi.SavePluginConfig(this);
this.Interface.SavePluginConfig(this);
}
}
}

View File

@ -0,0 +1,10 @@
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
VersionComponents="3"
ManifestType="yaml"
MakeZip="true"/>
</Target>
</Project>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Resourcer />
</Weavers>

View File

@ -9,18 +9,16 @@ namespace PeepingTom {
private delegate IntPtr GetListDelegate(IntPtr basePtr);
private delegate long RequestCharInfoDelegate(IntPtr ptr);
private readonly PeepingTomPlugin plugin;
private PeepingTomPlugin Plugin { get; }
private readonly RequestCharInfoDelegate _requestCharInfo = null;
private readonly RequestCharInfoDelegate? _requestCharInfo;
public GameFunctions(PeepingTomPlugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
IntPtr rciPtr;
try {
rciPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 40 BA 3B 01 00 00");
// old: 48 89 5C 24 ?? 57 48 83 EC 40 BA 1C 01 00 00
// old: 48 89 5C 24 ?? 57 48 83 EC 40 BA 1B 01 00 00
rciPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("E8 ?? ?? ?? ?? 83 7B 30 00 74 47");
} catch (KeyNotFoundException) {
rciPtr = IntPtr.Zero;
}
@ -32,8 +30,8 @@ namespace PeepingTom {
this._requestCharInfo = Marshal.GetDelegateForFunctionPointer<RequestCharInfoDelegate>(rciPtr);
}
private static IntPtr FollowPtrChain(IntPtr start, int[] offsets) {
foreach (int offset in offsets) {
private static IntPtr FollowPtrChain(IntPtr start, IEnumerable<int> offsets) {
foreach (var offset in offsets) {
start = Marshal.ReadIntPtr(start, offset);
if (start == IntPtr.Zero) {
break;
@ -48,12 +46,12 @@ namespace PeepingTom {
return;
}
IntPtr framework = this.plugin.Interface.Framework.Address.BaseAddress;
var framework = this.Plugin.Interface.Framework.Address.BaseAddress;
IntPtr getListPtr = FollowPtrChain(framework, new int[] { 0x29f8, 0, 0x110 });
var getListPtr = FollowPtrChain(framework, new[] { 0x29f8, 0, 0x110 });
var getList = Marshal.GetDelegateForFunctionPointer<GetListDelegate>(getListPtr);
IntPtr list = getList(Marshal.ReadIntPtr(framework + 0x29f8));
IntPtr rciData = Marshal.ReadIntPtr(list + 0x188);
var list = getList(Marshal.ReadIntPtr(framework + 0x29f8));
var rciData = Marshal.ReadIntPtr(list + 0x188);
Marshal.WriteInt32(rciData + 0x28, actor.ActorId);
Marshal.WriteInt32(rciData + 0x2c, actor.ActorId);

View File

@ -1,8 +0,0 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will add later and don't need warnings clogging up", Scope = "module")]

View File

@ -1,116 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" />
<Import Project="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" />
<Import Project="..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props" Condition="Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" />
<Import Project="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props" Condition="Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" />
<Import Project="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{888F98DF-AF1D-4852-8411-11B1FEEFE674}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFramework>net48</TargetFramework>
<RootNamespace>PeepingTom</RootNamespace>
<AssemblyVersion>1.5.2</AssemblyVersion>
<FileVersion>1.5.2</FileVersion>
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
<AssemblyName>PeepingTom</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<EmbeddedResource Include="Resources\target.wav" />
</ItemGroup>
<ItemGroup>
<Reference Include="Dalamud, Version=5.2.1.1, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGui.NET">
<Reference Include="ImGui.NET, Version=1.72.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGui.NET.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene">
<Reference Include="ImGuiScene, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\ImGuiScene.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="NAudio, Version=1.10.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.1.10.0\lib\net35\NAudio.dll</HintPath>
<Reference Include="SharpDX.Mathematics, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\SharpDX.Mathematics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SharpDX.Mathematics, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b4dcf0f35e5521f1" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration.cs" />
<Compile Include="GameFunctions.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="PluginUI.cs" />
<Compile Include="Targeting.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="TargetWatcher.cs" />
<Compile Include="Util.cs" />
<PackageReference Include="DalamudPackager" Version="1.0.1" />
<PackageReference Include="Fody" Version="6.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Resourcer.Fody" Version="1.8.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="NAudio" Version="1.10.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Resources\target.wav" />
</ItemGroup>
<ItemGroup>
<None Include="PeepingTom.json" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\cs\Microsoft.CodeAnalysis.VersionCheckAnalyzer.resources.dll" />
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\analyzers\dotnet\Microsoft.CodeAnalysis.VersionCheckAnalyzer.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Humanizer.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.CodeQuality.CSharp.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetCore.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetCore.CSharp.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.6\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.VersionCheckAnalyzer.2.9.6\build\Microsoft.CodeAnalysis.VersionCheckAnalyzer.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeQuality.Analyzers.2.9.6\build\Microsoft.CodeQuality.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetCore.Analyzers.2.9.6\build\Microsoft.NetCore.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.NetFramework.Analyzers.2.9.6\build\Microsoft.NetFramework.Analyzers.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.CodeAnalysis.FxCopAnalyzers.2.9.6\build\Microsoft.CodeAnalysis.FxCopAnalyzers.props'))" />
</Target>
</Project>
</Project>

View File

@ -1,10 +0,0 @@
{
"Author": "ascclemens",
"Name": "Peeping Tom",
"Description": "Shows who is or was targeting you. /ptom",
"InternalName": "PeepingTom",
"AssemblyVersion": "1.5.2",
"RepoUrl": "https://sr.ht/~jkcclemens/PeepingTom",
"ApplicableVersion": "any",
"DalamudApiLevel": 2
}

View File

@ -0,0 +1,4 @@
author: ascclemens
name: Peeping Tom
description: Shows who is currently or previously was targeting you.
repo_url: https://sr.ht/~jkcclemens/PeepingTom

View File

@ -3,14 +3,14 @@ using Dalamud.Plugin;
using System;
namespace PeepingTom {
public class PeepingTomPlugin : IDalamudPlugin, IDisposable {
public class PeepingTomPlugin : IDalamudPlugin {
public string Name => "Peeping Tom";
internal DalamudPluginInterface Interface { get; private set; }
internal Configuration Config { get; private set; }
internal PluginUI Ui { get; private set; }
internal TargetWatcher Watcher { get; private set; }
internal GameFunctions GameFunctions { get; private set; }
internal DalamudPluginInterface Interface { get; private set; } = null!;
internal Configuration Config { get; private set; } = null!;
internal PluginUi Ui { get; private set; } = null!;
internal TargetWatcher Watcher { get; private set; } = null!;
internal GameFunctions GameFunctions { get; private set; } = null!;
public void Initialize(DalamudPluginInterface pluginInterface) {
this.Interface = pluginInterface ?? throw new ArgumentNullException(nameof(pluginInterface), "DalamudPluginInterface argument was null");
@ -18,7 +18,7 @@ namespace PeepingTom {
this.Config.Initialize(this.Interface);
this.Watcher = new TargetWatcher(this);
this.GameFunctions = new GameFunctions(this);
this.Ui = new PluginUI(this);
this.Ui = new PluginUi(this);
this.Interface.CommandManager.AddHandler("/ppeepingtom", new CommandInfo(this.OnCommand) {
HelpMessage = "Use with no arguments to show the list. Use with \"c\" or \"config\" to show the config"
@ -33,8 +33,8 @@ namespace PeepingTom {
this.Interface.Framework.OnUpdateEvent += this.Watcher.OnFrameworkUpdate;
this.Interface.ClientState.OnLogin += this.OnLogin;
this.Interface.ClientState.OnLogout += this.OnLogout;
this.Interface.UiBuilder.OnBuildUi += this.DrawUI;
this.Interface.UiBuilder.OnOpenConfigUi += this.ConfigUI;
this.Interface.UiBuilder.OnBuildUi += this.DrawUi;
this.Interface.UiBuilder.OnOpenConfigUi += this.ConfigUi;
this.Watcher.StartThread();
}
@ -66,8 +66,8 @@ namespace PeepingTom {
this.Interface.ClientState.OnLogout -= this.OnLogout;
this.Watcher.WaitStopThread();
this.Watcher.Dispose();
this.Interface.UiBuilder.OnBuildUi -= DrawUI;
this.Interface.UiBuilder.OnOpenConfigUi -= ConfigUI;
this.Interface.UiBuilder.OnBuildUi -= this.DrawUi;
this.Interface.UiBuilder.OnOpenConfigUi -= this.ConfigUi;
this.Interface.CommandManager.RemoveHandler("/ppeepingtom");
this.Interface.CommandManager.RemoveHandler("/ptom");
this.Interface.CommandManager.RemoveHandler("/ppeep");
@ -79,11 +79,11 @@ namespace PeepingTom {
GC.SuppressFinalize(this);
}
private void DrawUI() {
private void DrawUi() {
this.Ui.Draw();
}
private void ConfigUI(object sender, EventArgs args) {
private void ConfigUi(object sender, EventArgs args) {
this.Ui.SettingsOpen = true;
}
}

View File

@ -1,559 +0,0 @@
using Dalamud.Game.Chat;
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using ImGuiNET;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
namespace PeepingTom {
class PluginUI : IDisposable {
private readonly PeepingTomPlugin plugin;
private Optional<Actor> previousFocus = new Optional<Actor>();
private bool _wantsOpen = false;
public bool WantsOpen {
get => this._wantsOpen;
set => this._wantsOpen = value;
}
public bool Visible { get; private set; }
private bool _settingsOpen = false;
public bool SettingsOpen {
get => this._settingsOpen;
set => this._settingsOpen = value;
}
public PluginUI(PeepingTomPlugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
}
public void Dispose() {
this.WantsOpen = false;
this.SettingsOpen = false;
}
public void Draw() {
if (this.SettingsOpen) {
ShowSettings();
}
bool inCombat = this.plugin.Interface.ClientState.Condition[ConditionFlag.InCombat];
bool inInstance = this.plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty]
|| this.plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty56]
|| this.plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty95];
bool inCutscene = this.plugin.Interface.ClientState.Condition[ConditionFlag.WatchingCutscene]
|| this.plugin.Interface.ClientState.Condition[ConditionFlag.WatchingCutscene78]
|| this.plugin.Interface.ClientState.Condition[ConditionFlag.OccupiedInCutSceneEvent];
// FIXME: this could just be a boolean expression
bool shouldBeShown = this.WantsOpen;
if (inCombat && !this.plugin.Config.ShowInCombat) {
shouldBeShown = false;
} else if (inInstance && !this.plugin.Config.ShowInInstance) {
shouldBeShown = false;
} else if (inCutscene && !this.plugin.Config.ShowInCutscenes) {
shouldBeShown = false;
}
this.Visible = shouldBeShown;
if (shouldBeShown) {
ShowMainWindow();
}
var flags = ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoNavInputs
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoNavFocus
| ImGuiWindowFlags.NoInputs
| ImGuiWindowFlags.NoMouseInputs
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoScrollWithMouse;
if (ImGui.Begin("Peeping Tom targeting indicator dummy window", flags)) {
if (this.plugin.Config.MarkTargeted) {
MarkPlayer(GetCurrentTarget(), this.plugin.Config.TargetedColour, this.plugin.Config.TargetedSize);
}
if (this.plugin.Config.MarkTargeting) {
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
if (player != null) {
PlayerCharacter[] targeting = this.plugin.Watcher.CurrentTargeters
.Select(targeter => this.plugin.Interface.ClientState.Actors.FirstOrDefault(actor => actor.ActorId == targeter.ActorId))
.Where(targeter => targeter != null)
.Select(targeter => targeter as PlayerCharacter)
.ToArray();
foreach (PlayerCharacter targeter in targeting) {
MarkPlayer(targeter, this.plugin.Config.TargetingColour, this.plugin.Config.TargetingSize);
}
}
}
}
}
private void ShowSettings() {
ImGui.SetNextWindowSize(new Vector2(700, 250));
if (ImGui.Begin($"{this.plugin.Name} settings", ref this._settingsOpen, ImGuiWindowFlags.NoResize)) {
if (ImGui.BeginTabBar("##settings-tabs")) {
if (ImGui.BeginTabItem("Markers")) {
bool markTargeted = this.plugin.Config.MarkTargeted;
if (ImGui.Checkbox("Mark your target", ref markTargeted)) {
this.plugin.Config.MarkTargeted = markTargeted;
this.plugin.Config.Save();
}
Vector4 targetedColour = this.plugin.Config.TargetedColour;
if (ImGui.ColorEdit4("Target mark colour", ref targetedColour)) {
this.plugin.Config.TargetedColour = targetedColour;
this.plugin.Config.Save();
}
float targetedSize = this.plugin.Config.TargetedSize;
if (ImGui.DragFloat("Target mark size", ref targetedSize, 0.01f, 0f, 15f)) {
targetedSize = Math.Max(0f, targetedSize);
this.plugin.Config.TargetedSize = targetedSize;
this.plugin.Config.Save();
}
ImGui.Spacing();
bool markTargeting = this.plugin.Config.MarkTargeting;
if (ImGui.Checkbox("Mark targeting you", ref markTargeting)) {
this.plugin.Config.MarkTargeting = markTargeting;
this.plugin.Config.Save();
}
Vector4 targetingColour = this.plugin.Config.TargetingColour;
if (ImGui.ColorEdit4("Targeting mark colour", ref targetingColour)) {
this.plugin.Config.TargetingColour = targetingColour;
this.plugin.Config.Save();
}
float targetingSize = this.plugin.Config.TargetingSize;
if (ImGui.DragFloat("Targeting mark size", ref targetingSize, 0.01f, 0f, 15f)) {
targetingSize = Math.Max(0f, targetingSize);
this.plugin.Config.TargetingSize = targetingSize;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Filters")) {
bool showParty = this.plugin.Config.LogParty;
if (ImGui.Checkbox("Log party members", ref showParty)) {
this.plugin.Config.LogParty = showParty;
this.plugin.Config.Save();
}
bool logAlliance = this.plugin.Config.LogAlliance;
if (ImGui.Checkbox("Log alliance members", ref logAlliance)) {
this.plugin.Config.LogAlliance = logAlliance;
this.plugin.Config.Save();
}
bool logInCombat = this.plugin.Config.LogInCombat;
if (ImGui.Checkbox("Log targeters engaged in combat", ref logInCombat)) {
this.plugin.Config.LogInCombat = logInCombat;
this.plugin.Config.Save();
}
bool logSelf = this.plugin.Config.LogSelf;
if (ImGui.Checkbox("Log yourself", ref logSelf)) {
this.plugin.Config.LogSelf = logSelf;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Behaviour")) {
bool focusTarget = this.plugin.Config.FocusTargetOnHover;
if (ImGui.Checkbox("Focus target on hover", ref focusTarget)) {
this.plugin.Config.FocusTargetOnHover = focusTarget;
this.plugin.Config.Save();
}
// bool openExamine = this.plugin.Config.OpenExamine;
// if (ImGui.Checkbox("Open examine window on Alt-click", ref openExamine)) {
// this.plugin.Config.OpenExamine = openExamine;
// this.plugin.Config.Save();
// }
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Sound")) {
bool playSound = this.plugin.Config.PlaySoundOnTarget;
if (ImGui.Checkbox("Play sound when targeted", ref playSound)) {
this.plugin.Config.PlaySoundOnTarget = playSound;
this.plugin.Config.Save();
}
string path = this.plugin.Config.SoundPath ?? "";
if (ImGui.InputText("Path to audio file", ref path, 1_000)) {
path = path.Trim();
this.plugin.Config.SoundPath = path.Length == 0 ? null : path;
this.plugin.Config.Save();
}
ImGui.Text("Leave this blank to use a built-in sound.");
float volume = this.plugin.Config.SoundVolume * 100f;
if (ImGui.DragFloat("Volume of sound", ref volume, .1f, 0f, 100f, "%.1f%%")) {
this.plugin.Config.SoundVolume = Math.Max(0f, Math.Min(1f, volume / 100f));
this.plugin.Config.Save();
}
int soundDevice = this.plugin.Config.SoundDevice;
string name;
if (soundDevice == -1) {
name = "Default";
} else if (soundDevice > -1 && soundDevice < WaveOut.DeviceCount) {
var caps = WaveOut.GetCapabilities(soundDevice);
name = caps.ProductName;
} else {
name = "Invalid device";
}
if (ImGui.BeginCombo("Output device", name)) {
if (ImGui.Selectable("Default")) {
this.plugin.Config.SoundDevice = -1;
this.plugin.Config.Save();
}
ImGui.Separator();
for (int deviceNum = 0; deviceNum < WaveOut.DeviceCount; deviceNum++) {
var caps = WaveOut.GetCapabilities(deviceNum);
if (ImGui.Selectable(caps.ProductName)) {
this.plugin.Config.SoundDevice = deviceNum;
this.plugin.Config.Save();
}
}
ImGui.EndCombo();
}
float soundCooldown = this.plugin.Config.SoundCooldown;
if (ImGui.DragFloat("Cooldown for sound (seconds)", ref soundCooldown, .01f, 0f, 30f)) {
soundCooldown = Math.Max(0f, soundCooldown);
this.plugin.Config.SoundCooldown = soundCooldown;
this.plugin.Config.Save();
}
bool playWhenClosed = this.plugin.Config.PlaySoundWhenClosed;
if (ImGui.Checkbox("Play sound when window is closed", ref playWhenClosed)) {
this.plugin.Config.PlaySoundWhenClosed = playWhenClosed;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Window")) {
bool openOnLogin = this.plugin.Config.OpenOnLogin;
if (ImGui.Checkbox("Open on login", ref openOnLogin)) {
this.plugin.Config.OpenOnLogin = openOnLogin;
this.plugin.Config.Save();
}
bool allowMovement = this.plugin.Config.AllowMovement;
if (ImGui.Checkbox("Allow moving the main window", ref allowMovement)) {
this.plugin.Config.AllowMovement = allowMovement;
this.plugin.Config.Save();
}
bool allowResizing = this.plugin.Config.AllowResize;
if (ImGui.Checkbox("Allow resizing the main window", ref allowResizing)) {
this.plugin.Config.AllowResize = allowResizing;
this.plugin.Config.Save();
}
ImGui.Spacing();
bool showInCombat = this.plugin.Config.ShowInCombat;
if (ImGui.Checkbox("Show window while in combat", ref showInCombat)) {
this.plugin.Config.ShowInCombat = showInCombat;
this.plugin.Config.Save();
}
bool showInInstance = this.plugin.Config.ShowInInstance;
if (ImGui.Checkbox("Show window while in instance", ref showInInstance)) {
this.plugin.Config.ShowInInstance = showInInstance;
this.plugin.Config.Save();
}
bool showInCutscenes = this.plugin.Config.ShowInCutscenes;
if (ImGui.Checkbox("Show window while in cutscenes", ref showInCutscenes)) {
this.plugin.Config.ShowInCutscenes = showInCutscenes;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("History")) {
bool keepHistory = this.plugin.Config.KeepHistory;
if (ImGui.Checkbox("Show previous targeters", ref keepHistory)) {
this.plugin.Config.KeepHistory = keepHistory;
this.plugin.Config.Save();
}
bool historyWhenClosed = this.plugin.Config.HistoryWhenClosed;
if (ImGui.Checkbox("Record history when window is closed", ref historyWhenClosed)) {
this.plugin.Config.HistoryWhenClosed = historyWhenClosed;
this.plugin.Config.Save();
}
int numHistory = this.plugin.Config.NumHistory;
if (ImGui.InputInt("Number of previous targeters to keep", ref numHistory)) {
numHistory = Math.Max(0, Math.Min(50, numHistory));
this.plugin.Config.NumHistory = numHistory;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Advanced")) {
int pollFrequency = this.plugin.Config.PollFrequency;
if (ImGui.DragInt("Poll frequency in milliseconds", ref pollFrequency, .1f, 1, 1600)) {
this.plugin.Config.PollFrequency = pollFrequency;
this.plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Debug")) {
if (ImGui.Button("Log targeting you")) {
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
if (player != null) {
// loop over all players looking at the current player
var actors = this.plugin.Interface.ClientState.Actors
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
.Select(actor => actor as PlayerCharacter);
foreach (PlayerCharacter actor in actors) {
PlayerPayload payload = new PlayerPayload(this.plugin.Interface.Data, actor.Name, actor.HomeWorld.Id);
Payload[] payloads = { payload };
this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode()
});
}
}
}
if (ImGui.Button("Log your target")) {
PlayerCharacter target = GetCurrentTarget();
if (target != null) {
PlayerPayload payload = new PlayerPayload(this.plugin.Interface.Data, target.Name, target.HomeWorld.Id);
Payload[] payloads = { payload };
this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode()
});
}
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.End();
}
}
private void ShowMainWindow() {
IReadOnlyCollection<Targeter> targeting = this.plugin.Watcher.CurrentTargeters;
IReadOnlyCollection<Targeter> previousTargeters = this.plugin.Config.KeepHistory ? this.plugin.Watcher.PreviousTargeters : null;
// to prevent looping over a subset of the actors repeatedly when multiple people are targeting,
// create a dictionary for O(1) lookups by actor id
Dictionary<int, Actor> actors = null;
if (targeting.Count + (previousTargeters?.Count ?? 0) > 1) {
Dictionary<int, Actor> dict = new Dictionary<int, Actor>();
foreach (Actor actor in this.plugin.Interface.ClientState.Actors) {
if (dict.ContainsKey(actor.ActorId) || actor.ObjectKind != Dalamud.Game.ClientState.Actors.ObjectKind.Player) {
continue;
}
dict.Add(actor.ActorId, actor);
}
actors = dict;
}
ImGuiWindowFlags flags = ImGuiWindowFlags.None;
if (!this.plugin.Config.AllowMovement) {
flags |= ImGuiWindowFlags.NoMove;
}
if (!this.plugin.Config.AllowResize) {
flags |= ImGuiWindowFlags.NoResize;
}
ImGui.SetNextWindowSize(new Vector2(290, 195), ImGuiCond.FirstUseEver);
if (ImGui.Begin(this.plugin.Name, ref this._wantsOpen, flags)) {
ImGui.Text("Targeting you");
ImGui.SameLine();
// if (this.plugin.Config.OpenExamine) {
// HelpMarker("Click to link, Alt-click to examine, or right click to target.");
// } else {
HelpMarker("Click to link or right click to target.");
// }
float height = ImGui.GetContentRegionAvail().Y;
height -= ImGui.GetStyle().ItemSpacing.Y;
bool anyHovered = false;
if (ImGui.ListBoxHeader("##targeting", new Vector2(-1, height))) {
// add the two first players for testing
//foreach (PlayerCharacter p in this.plugin.Interface.ClientState.Actors
// .Where(actor => actor is PlayerCharacter)
// .Skip(1)
// .Select(actor => actor as PlayerCharacter)
// .Take(2)) {
// this.AddEntry(new Targeter(p), p, ref anyHovered);
//}
foreach (Targeter targeter in targeting) {
Actor actor = null;
actors?.TryGetValue(targeter.ActorId, out actor);
this.AddEntry(targeter, actor, ref anyHovered);
}
if (this.plugin.Config.KeepHistory) {
// get a list of the previous targeters that aren't currently targeting
var previous = previousTargeters
.Where(old => targeting.All(actor => actor.ActorId != old.ActorId))
.Take(this.plugin.Config.NumHistory);
// add previous targeters to the list
foreach (Targeter oldTargeter in previous) {
Actor actor = null;
actors?.TryGetValue(oldTargeter.ActorId, out actor);
this.AddEntry(oldTargeter, actor, ref anyHovered, ImGuiSelectableFlags.Disabled);
}
}
ImGui.ListBoxFooter();
}
if (this.plugin.Config.FocusTargetOnHover && !anyHovered && this.previousFocus.Get(out Actor previousFocus)) {
if (previousFocus == null) {
this.plugin.Interface.ClientState.Targets.SetFocusTarget(null);
} else {
Actor actor = this.plugin.Interface.ClientState.Actors.FirstOrDefault(a => a.ActorId == previousFocus.ActorId);
// either target the actor if still present or target nothing
this.plugin.Interface.ClientState.Targets.SetFocusTarget(actor);
}
this.previousFocus = new Optional<Actor>();
}
ImGui.End();
}
}
private static void HelpMarker(string text) {
ImGui.TextDisabled("(?)");
if (ImGui.IsItemHovered()) {
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
}
private void AddEntry(Targeter targeter, Actor actor, ref bool anyHovered, ImGuiSelectableFlags flags = ImGuiSelectableFlags.None) {
ImGui.Selectable(targeter.Name, false, flags);
bool hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled);
bool left = hover && ImGui.IsMouseClicked(0);
bool right = hover && ImGui.IsMouseClicked(1);
if (actor == null) {
actor = this.plugin.Interface.ClientState.Actors
.Where(a => a.ActorId == targeter.ActorId)
.FirstOrDefault();
}
// don't count as hovered if the actor isn't here (clears focus target when hovering missing actors)
if (actor != null) {
anyHovered |= hover;
}
if (this.plugin.Config.FocusTargetOnHover && hover && actor != null) {
if (!this.previousFocus.Present) {
this.previousFocus = new Optional<Actor>(this.plugin.Interface.ClientState.Targets.FocusTarget);
}
this.plugin.Interface.ClientState.Targets.SetFocusTarget(actor);
}
if (left) {
if (false && this.plugin.Config.OpenExamine && ImGui.GetIO().KeyAlt) {
if (actor != null) {
this.plugin.GameFunctions.OpenExamineWindow(actor);
} else {
Payload[] payloads = {
new TextPayload($"[{this.plugin.Name}] "),
new PlayerPayload(this.plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id),
new TextPayload(" is not close enough to examine."),
};
this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode(),
});
}
} else {
PlayerPayload payload = new PlayerPayload(this.plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id);
Payload[] payloads = { payload };
this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode(),
});
}
} else if (right && actor != null) {
this.plugin.Interface.ClientState.Targets.SetCurrentTarget(actor);
}
}
private void MarkPlayer(PlayerCharacter player, Vector4 colour, float size) {
if (player == null) {
return;
}
if (!this.plugin.Interface.Framework.Gui.WorldToScreen(player.Position, out SharpDX.Vector2 screenPos)) {
return;
}
ImGui.PushClipRect(new Vector2(0, 0), ImGui.GetIO().DisplaySize, false);
ImGui.GetWindowDrawList().AddCircleFilled(
new Vector2(screenPos.X, screenPos.Y),
size,
ImGui.GetColorU32(colour),
100
);
ImGui.PopClipRect();
}
private PlayerCharacter GetCurrentTarget() {
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
if (player == null) {
return null;
}
int targetId = player.TargetActorID;
if (targetId <= 0) {
return null;
}
return this.plugin.Interface.ClientState.Actors
.Where(actor => actor.ActorId == targetId && actor is PlayerCharacter)
.Select(actor => actor as PlayerCharacter)
.FirstOrDefault();
}
}
}

571
Peeping Tom/PluginUi.cs Normal file
View File

@ -0,0 +1,571 @@
using Dalamud.Game.Chat;
using Dalamud.Game.Chat.SeStringHandling;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Actors.Types;
using ImGuiNET;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace PeepingTom {
internal class PluginUi : IDisposable {
private PeepingTomPlugin Plugin { get; }
private Optional<Actor> PreviousFocus { get; set; } = new Optional<Actor>();
private bool _wantsOpen;
public bool WantsOpen {
get => this._wantsOpen;
set => this._wantsOpen = value;
}
public bool Visible { get; private set; }
private bool _settingsOpen;
public bool SettingsOpen {
get => this._settingsOpen;
set => this._settingsOpen = value;
}
public PluginUi(PeepingTomPlugin plugin) {
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
}
public void Dispose() {
this.WantsOpen = false;
this.SettingsOpen = false;
}
public void Draw() {
if (this.SettingsOpen) {
this.ShowSettings();
}
var inCombat = this.Plugin.Interface.ClientState.Condition[ConditionFlag.InCombat];
var inInstance = this.Plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty]
|| this.Plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty56]
|| this.Plugin.Interface.ClientState.Condition[ConditionFlag.BoundByDuty95];
var inCutscene = this.Plugin.Interface.ClientState.Condition[ConditionFlag.WatchingCutscene]
|| this.Plugin.Interface.ClientState.Condition[ConditionFlag.WatchingCutscene78]
|| this.Plugin.Interface.ClientState.Condition[ConditionFlag.OccupiedInCutSceneEvent];
// FIXME: this could just be a boolean expression
var shouldBeShown = this.WantsOpen;
if (inCombat && !this.Plugin.Config.ShowInCombat) {
shouldBeShown = false;
} else if (inInstance && !this.Plugin.Config.ShowInInstance) {
shouldBeShown = false;
} else if (inCutscene && !this.Plugin.Config.ShowInCutscenes) {
shouldBeShown = false;
}
this.Visible = shouldBeShown;
if (shouldBeShown) {
this.ShowMainWindow();
}
const ImGuiWindowFlags flags = ImGuiWindowFlags.NoBackground
| ImGuiWindowFlags.NoTitleBar
| ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoNavInputs
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoNavFocus
| ImGuiWindowFlags.NoInputs
| ImGuiWindowFlags.NoMouseInputs
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.NoScrollWithMouse;
if (!ImGui.Begin("Peeping Tom targeting indicator dummy window", flags)) {
return;
}
if (this.Plugin.Config.MarkTargeted) {
this.MarkPlayer(this.GetCurrentTarget(), this.Plugin.Config.TargetedColour, this.Plugin.Config.TargetedSize);
}
if (!this.Plugin.Config.MarkTargeting) {
return;
}
var player = this.Plugin.Interface.ClientState.LocalPlayer;
if (player == null) {
return;
}
var targeting = this.Plugin.Watcher.CurrentTargeters
.Select(targeter => this.Plugin.Interface.ClientState.Actors.FirstOrDefault(actor => actor.ActorId == targeter.ActorId))
.Where(targeter => targeter != null)
.Cast<PlayerCharacter>()
.ToArray();
foreach (var targeter in targeting) {
this.MarkPlayer(targeter, this.Plugin.Config.TargetingColour, this.Plugin.Config.TargetingSize);
}
}
private void ShowSettings() {
ImGui.SetNextWindowSize(new Vector2(700, 250));
if (!ImGui.Begin($"{this.Plugin.Name} settings", ref this._settingsOpen, ImGuiWindowFlags.NoResize)) {
return;
}
if (ImGui.BeginTabBar("##settings-tabs")) {
if (ImGui.BeginTabItem("Markers")) {
var markTargeted = this.Plugin.Config.MarkTargeted;
if (ImGui.Checkbox("Mark your target", ref markTargeted)) {
this.Plugin.Config.MarkTargeted = markTargeted;
this.Plugin.Config.Save();
}
var targetedColour = this.Plugin.Config.TargetedColour;
if (ImGui.ColorEdit4("Target mark colour", ref targetedColour)) {
this.Plugin.Config.TargetedColour = targetedColour;
this.Plugin.Config.Save();
}
var targetedSize = this.Plugin.Config.TargetedSize;
if (ImGui.DragFloat("Target mark size", ref targetedSize, 0.01f, 0f, 15f)) {
targetedSize = Math.Max(0f, targetedSize);
this.Plugin.Config.TargetedSize = targetedSize;
this.Plugin.Config.Save();
}
ImGui.Spacing();
var markTargeting = this.Plugin.Config.MarkTargeting;
if (ImGui.Checkbox("Mark targeting you", ref markTargeting)) {
this.Plugin.Config.MarkTargeting = markTargeting;
this.Plugin.Config.Save();
}
var targetingColour = this.Plugin.Config.TargetingColour;
if (ImGui.ColorEdit4("Targeting mark colour", ref targetingColour)) {
this.Plugin.Config.TargetingColour = targetingColour;
this.Plugin.Config.Save();
}
var targetingSize = this.Plugin.Config.TargetingSize;
if (ImGui.DragFloat("Targeting mark size", ref targetingSize, 0.01f, 0f, 15f)) {
targetingSize = Math.Max(0f, targetingSize);
this.Plugin.Config.TargetingSize = targetingSize;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Filters")) {
var showParty = this.Plugin.Config.LogParty;
if (ImGui.Checkbox("Log party members", ref showParty)) {
this.Plugin.Config.LogParty = showParty;
this.Plugin.Config.Save();
}
var logAlliance = this.Plugin.Config.LogAlliance;
if (ImGui.Checkbox("Log alliance members", ref logAlliance)) {
this.Plugin.Config.LogAlliance = logAlliance;
this.Plugin.Config.Save();
}
var logInCombat = this.Plugin.Config.LogInCombat;
if (ImGui.Checkbox("Log targeters engaged in combat", ref logInCombat)) {
this.Plugin.Config.LogInCombat = logInCombat;
this.Plugin.Config.Save();
}
var logSelf = this.Plugin.Config.LogSelf;
if (ImGui.Checkbox("Log yourself", ref logSelf)) {
this.Plugin.Config.LogSelf = logSelf;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Behaviour")) {
var focusTarget = this.Plugin.Config.FocusTargetOnHover;
if (ImGui.Checkbox("Focus target on hover", ref focusTarget)) {
this.Plugin.Config.FocusTargetOnHover = focusTarget;
this.Plugin.Config.Save();
}
// bool openExamine = this.plugin.Config.OpenExamine;
// if (ImGui.Checkbox("Open examine window on Alt-click", ref openExamine)) {
// this.plugin.Config.OpenExamine = openExamine;
// this.plugin.Config.Save();
// }
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Sound")) {
var playSound = this.Plugin.Config.PlaySoundOnTarget;
if (ImGui.Checkbox("Play sound when targeted", ref playSound)) {
this.Plugin.Config.PlaySoundOnTarget = playSound;
this.Plugin.Config.Save();
}
var path = this.Plugin.Config.SoundPath ?? "";
if (ImGui.InputText("Path to audio file", ref path, 1_000)) {
path = path.Trim();
this.Plugin.Config.SoundPath = path.Length == 0 ? null : path;
this.Plugin.Config.Save();
}
ImGui.Text("Leave this blank to use a built-in sound.");
var volume = this.Plugin.Config.SoundVolume * 100f;
if (ImGui.DragFloat("Volume of sound", ref volume, .1f, 0f, 100f, "%.1f%%")) {
this.Plugin.Config.SoundVolume = Math.Max(0f, Math.Min(1f, volume / 100f));
this.Plugin.Config.Save();
}
var soundDevice = this.Plugin.Config.SoundDevice;
string name;
if (soundDevice == -1) {
name = "Default";
} else if (soundDevice > -1 && soundDevice < WaveOut.DeviceCount) {
var caps = WaveOut.GetCapabilities(soundDevice);
name = caps.ProductName;
} else {
name = "Invalid device";
}
if (ImGui.BeginCombo("Output device", name)) {
if (ImGui.Selectable("Default")) {
this.Plugin.Config.SoundDevice = -1;
this.Plugin.Config.Save();
}
ImGui.Separator();
for (var deviceNum = 0; deviceNum < WaveOut.DeviceCount; deviceNum++) {
var caps = WaveOut.GetCapabilities(deviceNum);
if (!ImGui.Selectable(caps.ProductName)) {
continue;
}
this.Plugin.Config.SoundDevice = deviceNum;
this.Plugin.Config.Save();
}
ImGui.EndCombo();
}
var soundCooldown = this.Plugin.Config.SoundCooldown;
if (ImGui.DragFloat("Cooldown for sound (seconds)", ref soundCooldown, .01f, 0f, 30f)) {
soundCooldown = Math.Max(0f, soundCooldown);
this.Plugin.Config.SoundCooldown = soundCooldown;
this.Plugin.Config.Save();
}
var playWhenClosed = this.Plugin.Config.PlaySoundWhenClosed;
if (ImGui.Checkbox("Play sound when window is closed", ref playWhenClosed)) {
this.Plugin.Config.PlaySoundWhenClosed = playWhenClosed;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Window")) {
var openOnLogin = this.Plugin.Config.OpenOnLogin;
if (ImGui.Checkbox("Open on login", ref openOnLogin)) {
this.Plugin.Config.OpenOnLogin = openOnLogin;
this.Plugin.Config.Save();
}
var allowMovement = this.Plugin.Config.AllowMovement;
if (ImGui.Checkbox("Allow moving the main window", ref allowMovement)) {
this.Plugin.Config.AllowMovement = allowMovement;
this.Plugin.Config.Save();
}
var allowResizing = this.Plugin.Config.AllowResize;
if (ImGui.Checkbox("Allow resizing the main window", ref allowResizing)) {
this.Plugin.Config.AllowResize = allowResizing;
this.Plugin.Config.Save();
}
ImGui.Spacing();
var showInCombat = this.Plugin.Config.ShowInCombat;
if (ImGui.Checkbox("Show window while in combat", ref showInCombat)) {
this.Plugin.Config.ShowInCombat = showInCombat;
this.Plugin.Config.Save();
}
var showInInstance = this.Plugin.Config.ShowInInstance;
if (ImGui.Checkbox("Show window while in instance", ref showInInstance)) {
this.Plugin.Config.ShowInInstance = showInInstance;
this.Plugin.Config.Save();
}
var showInCutscenes = this.Plugin.Config.ShowInCutscenes;
if (ImGui.Checkbox("Show window while in cutscenes", ref showInCutscenes)) {
this.Plugin.Config.ShowInCutscenes = showInCutscenes;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("History")) {
var keepHistory = this.Plugin.Config.KeepHistory;
if (ImGui.Checkbox("Show previous targeters", ref keepHistory)) {
this.Plugin.Config.KeepHistory = keepHistory;
this.Plugin.Config.Save();
}
var historyWhenClosed = this.Plugin.Config.HistoryWhenClosed;
if (ImGui.Checkbox("Record history when window is closed", ref historyWhenClosed)) {
this.Plugin.Config.HistoryWhenClosed = historyWhenClosed;
this.Plugin.Config.Save();
}
var numHistory = this.Plugin.Config.NumHistory;
if (ImGui.InputInt("Number of previous targeters to keep", ref numHistory)) {
numHistory = Math.Max(0, Math.Min(50, numHistory));
this.Plugin.Config.NumHistory = numHistory;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Advanced")) {
var pollFrequency = this.Plugin.Config.PollFrequency;
if (ImGui.DragInt("Poll frequency in milliseconds", ref pollFrequency, .1f, 1, 1600)) {
this.Plugin.Config.PollFrequency = pollFrequency;
this.Plugin.Config.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Debug")) {
if (ImGui.Button("Log targeting you")) {
var player = this.Plugin.Interface.ClientState.LocalPlayer;
if (player != null) {
// loop over all players looking at the current player
var actors = this.Plugin.Interface.ClientState.Actors
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
.Cast<PlayerCharacter>();
foreach (var actor in actors) {
var payload = new PlayerPayload(this.Plugin.Interface.Data, actor.Name, actor.HomeWorld.Id);
Payload[] payloads = { payload };
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode()
});
}
}
}
if (ImGui.Button("Log your target")) {
var target = this.GetCurrentTarget();
if (target != null) {
var payload = new PlayerPayload(this.Plugin.Interface.Data, target.Name, target.HomeWorld.Id);
Payload[] payloads = { payload };
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode()
});
}
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
ImGui.End();
}
private void ShowMainWindow() {
var targeting = this.Plugin.Watcher.CurrentTargeters;
var previousTargeters = this.Plugin.Config.KeepHistory ? this.Plugin.Watcher.PreviousTargeters : null;
// to prevent looping over a subset of the actors repeatedly when multiple people are targeting,
// create a dictionary for O(1) lookups by actor id
Dictionary<int, Actor>? actors = null;
if (targeting.Count + (previousTargeters?.Count ?? 0) > 1) {
var dict = new Dictionary<int, Actor>();
foreach (var actor in this.Plugin.Interface.ClientState.Actors) {
if (dict.ContainsKey(actor.ActorId) || actor.ObjectKind != Dalamud.Game.ClientState.Actors.ObjectKind.Player) {
continue;
}
dict.Add(actor.ActorId, actor);
}
actors = dict;
}
var flags = ImGuiWindowFlags.None;
if (!this.Plugin.Config.AllowMovement) {
flags |= ImGuiWindowFlags.NoMove;
}
if (!this.Plugin.Config.AllowResize) {
flags |= ImGuiWindowFlags.NoResize;
}
ImGui.SetNextWindowSize(new Vector2(290, 195), ImGuiCond.FirstUseEver);
if (!ImGui.Begin(this.Plugin.Name, ref this._wantsOpen, flags)) {
return;
}
{
ImGui.Text("Targeting you");
ImGui.SameLine();
// if (this.plugin.Config.OpenExamine) {
// HelpMarker("Click to link, Alt-click to examine, or right click to target.");
// } else {
HelpMarker("Click to link or right click to target.");
// }
var height = ImGui.GetContentRegionAvail().Y;
height -= ImGui.GetStyle().ItemSpacing.Y;
var anyHovered = false;
if (ImGui.ListBoxHeader("##targeting", new Vector2(-1, height))) {
// add the two first players for testing
//foreach (PlayerCharacter p in this.plugin.Interface.ClientState.Actors
// .Where(actor => actor is PlayerCharacter)
// .Skip(1)
// .Select(actor => actor as PlayerCharacter)
// .Take(2)) {
// this.AddEntry(new Targeter(p), p, ref anyHovered);
//}
foreach (var targeter in targeting) {
Actor? actor = null;
actors?.TryGetValue(targeter.ActorId, out actor);
this.AddEntry(targeter, actor, ref anyHovered);
}
if (this.Plugin.Config.KeepHistory) {
// get a list of the previous targeters that aren't currently targeting
var previous = (previousTargeters ?? new List<Targeter>())
.Where(old => targeting.All(actor => actor.ActorId != old.ActorId))
.Take(this.Plugin.Config.NumHistory);
// add previous targeters to the list
foreach (var oldTargeter in previous) {
Actor? actor = null;
actors?.TryGetValue(oldTargeter.ActorId, out actor);
this.AddEntry(oldTargeter, actor, ref anyHovered, ImGuiSelectableFlags.Disabled);
}
}
ImGui.ListBoxFooter();
}
if (this.Plugin.Config.FocusTargetOnHover && !anyHovered && this.PreviousFocus.Get(out var previousFocus)) {
if (previousFocus == null) {
this.Plugin.Interface.ClientState.Targets.SetFocusTarget(null);
} else {
var actor = this.Plugin.Interface.ClientState.Actors.FirstOrDefault(a => a.ActorId == previousFocus.ActorId);
// either target the actor if still present or target nothing
this.Plugin.Interface.ClientState.Targets.SetFocusTarget(actor);
}
this.PreviousFocus = new Optional<Actor>();
}
ImGui.End();
}
}
private static void HelpMarker(string text) {
ImGui.TextDisabled("(?)");
if (!ImGui.IsItemHovered()) {
return;
}
ImGui.BeginTooltip();
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 20f);
ImGui.TextUnformatted(text);
ImGui.PopTextWrapPos();
ImGui.EndTooltip();
}
private void AddEntry(Targeter targeter, Actor? actor, ref bool anyHovered, ImGuiSelectableFlags flags = ImGuiSelectableFlags.None) {
ImGui.Selectable(targeter.Name, false, flags);
var hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled);
var left = hover && ImGui.IsMouseClicked(0);
var right = hover && ImGui.IsMouseClicked(1);
actor ??= this.Plugin.Interface.ClientState.Actors
.FirstOrDefault(a => a.ActorId == targeter.ActorId);
// don't count as hovered if the actor isn't here (clears focus target when hovering missing actors)
if (actor != null) {
anyHovered |= hover;
}
if (this.Plugin.Config.FocusTargetOnHover && hover && actor != null) {
if (!this.PreviousFocus.Present) {
this.PreviousFocus = new Optional<Actor>(this.Plugin.Interface.ClientState.Targets.FocusTarget);
}
this.Plugin.Interface.ClientState.Targets.SetFocusTarget(actor);
}
if (left) {
if (false && this.Plugin.Config.OpenExamine && ImGui.GetIO().KeyAlt) {
if (actor != null) {
this.Plugin.GameFunctions.OpenExamineWindow(actor);
} else {
Payload[] payloads = {
new TextPayload($"[{this.Plugin.Name}] "),
new PlayerPayload(this.Plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id),
new TextPayload(" is not close enough to examine."),
};
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode(),
});
}
} else {
var payload = new PlayerPayload(this.Plugin.Interface.Data, targeter.Name, targeter.HomeWorld.Id);
Payload[] payloads = { payload };
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode(),
});
}
} else if (right && actor != null) {
this.Plugin.Interface.ClientState.Targets.SetCurrentTarget(actor);
}
}
private void MarkPlayer(Actor? player, Vector4 colour, float size) {
if (player == null) {
return;
}
if (!this.Plugin.Interface.Framework.Gui.WorldToScreen(player.Position, out var screenPos)) {
return;
}
ImGui.PushClipRect(new Vector2(0, 0), ImGui.GetIO().DisplaySize, false);
ImGui.GetWindowDrawList().AddCircleFilled(
new Vector2(screenPos.X, screenPos.Y),
size,
ImGui.GetColorU32(colour),
100
);
ImGui.PopClipRect();
}
private PlayerCharacter? GetCurrentTarget() {
var player = this.Plugin.Interface.ClientState.LocalPlayer;
if (player == null) {
return null;
}
var targetId = player.TargetActorID;
if (targetId <= 0) {
return null;
}
return this.Plugin.Interface.ClientState.Actors
.Where(actor => actor.ActorId == targetId && actor is PlayerCharacter)
.Select(actor => actor as PlayerCharacter)
.FirstOrDefault();
}
}
}

View File

@ -1,38 +0,0 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Peeping Tom")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Peeping Tom")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("888f98df-af1d-4852-8411-11b1feefe674")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.5.2")]
[assembly: AssemblyFileVersion("1.5.2")]
[assembly: NeutralResourcesLanguage("en-GB")]

View File

@ -1,72 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PeepingTom.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PeepingTom.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.IO.UnmanagedMemoryStream similar to System.IO.MemoryStream.
/// </summary>
internal static System.IO.UnmanagedMemoryStream Target {
get {
return ResourceManager.GetStream("Target", resourceCulture);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.Internal;
using Dalamud.Plugin;
using NAudio.Wave;
using Resourcer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -13,148 +14,148 @@ using System.Runtime.InteropServices;
using System.Threading;
namespace PeepingTom {
class TargetWatcher : IDisposable {
private readonly PeepingTomPlugin plugin;
internal class TargetWatcher : IDisposable {
private PeepingTomPlugin Plugin { get; }
private Stopwatch watch = null;
private int lastTargetAmount = 0;
private Stopwatch? Watch { get; set; }
private int LastTargetAmount { get; set; }
private volatile bool stop = false;
private volatile bool needsUpdate = true;
private Thread thread;
private volatile bool _stop;
private volatile bool _needsUpdate = true;
private Thread? Thread { get; set; }
private readonly object dataMutex = new object();
private TargetThreadData data;
private readonly object _dataMutex = new object();
private TargetThreadData? Data { get; set; }
private readonly Mutex _currentMutex = new Mutex();
private Targeter[] Current { get; set; } = Array.Empty<Targeter>();
private readonly Mutex currentMutex = new Mutex();
private Targeter[] current = Array.Empty<Targeter>();
public IReadOnlyCollection<Targeter> CurrentTargeters {
get {
this.currentMutex.WaitOne();
Targeter[] current = this.current.ToArray();
this.currentMutex.ReleaseMutex();
this._currentMutex.WaitOne();
var current = this.Current.ToArray();
this._currentMutex.ReleaseMutex();
return current;
}
}
private readonly Mutex previousMutex = new Mutex();
private readonly List<Targeter> previousTargeters = new List<Targeter>();
private readonly Mutex _previousMutex = new Mutex();
private List<Targeter> Previous { get; set; } = new List<Targeter>();
public IReadOnlyCollection<Targeter> PreviousTargeters {
get {
this.previousMutex.WaitOne();
Targeter[] previous = this.previousTargeters.ToArray();
this.previousMutex.ReleaseMutex();
this._previousMutex.WaitOne();
var previous = this.Previous.ToArray();
this._previousMutex.ReleaseMutex();
return previous;
}
}
public TargetWatcher(PeepingTomPlugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "PeepingTomPlugin cannot be null");
}
public void ClearPrevious() {
this.previousMutex.WaitOne();
this.previousTargeters.Clear();
this.previousMutex.ReleaseMutex();
this._previousMutex.WaitOne();
this.Previous.Clear();
this._previousMutex.ReleaseMutex();
}
public void StartThread() {
this.thread = new Thread(new ThreadStart(() => {
while (!this.stop) {
this.Thread = new Thread(() => {
while (!this._stop) {
this.Update();
this.needsUpdate = true;
Thread.Sleep(this.plugin.Config.PollFrequency);
this._needsUpdate = true;
Thread.Sleep(this.Plugin.Config.PollFrequency);
}
}));
this.thread.Start();
});
this.Thread.Start();
}
public void WaitStopThread() {
this.stop = true;
this.thread?.Join();
this._stop = true;
this.Thread?.Join();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "delegate")]
public void OnFrameworkUpdate(Framework framework) {
if (!this.needsUpdate) {
if (!this._needsUpdate) {
return;
}
lock (this.dataMutex) {
this.data = new TargetThreadData(this.plugin.Interface);
lock (this._dataMutex) {
this.Data = new TargetThreadData(this.Plugin.Interface);
}
this.needsUpdate = false;
this._needsUpdate = false;
}
private void Update() {
lock (this.dataMutex) {
if (this.data == null) {
return;
}
PlayerCharacter player = this.data.localPlayer;
lock (this._dataMutex) {
var player = this.Data?.LocalPlayer;
if (player == null) {
return;
}
// block until lease
this.currentMutex.WaitOne();
this._currentMutex.WaitOne();
// get targeters and set a copy so we can release the mutex faster
Targeter[] current = this.GetTargeting(this.data.actors, player);
this.current = (Targeter[])current.Clone();
var current = this.GetTargeting(this.Data!.Actors, player);
this.Current = (Targeter[]) current.Clone();
// release
this.currentMutex.ReleaseMutex();
this._currentMutex.ReleaseMutex();
}
this.HandleHistory(current);
this.HandleHistory(this.Current);
// play sound if necessary
if (this.CanPlaySound()) {
this.watch.Restart();
this.Watch?.Restart();
this.PlaySound();
}
this.lastTargetAmount = this.current.Length;
this.LastTargetAmount = this.Current.Length;
}
private void HandleHistory(Targeter[] targeting) {
if (!this.plugin.Config.KeepHistory || (!this.plugin.Config.HistoryWhenClosed && !this.plugin.Ui.Visible)) {
if (!this.Plugin.Config.KeepHistory || (!this.Plugin.Config.HistoryWhenClosed && !this.Plugin.Ui.Visible)) {
return;
}
this.previousMutex.WaitOne();
this._previousMutex.WaitOne();
foreach (Targeter targeter in targeting) {
foreach (var targeter in targeting) {
// add the targeter to the previous list
if (this.previousTargeters.Any(old => old.ActorId == targeter.ActorId)) {
this.previousTargeters.RemoveAll(old => old.ActorId == targeter.ActorId);
if (this.Previous.Any(old => old.ActorId == targeter.ActorId)) {
this.Previous.RemoveAll(old => old.ActorId == targeter.ActorId);
}
this.previousTargeters.Insert(0, targeter);
this.Previous.Insert(0, targeter);
}
// only keep the configured number of previous targeters (ignoring ones that are currently targeting)
while (this.previousTargeters.Where(old => targeting.All(actor => actor.ActorId != old.ActorId)).Count() > this.plugin.Config.NumHistory) {
this.previousTargeters.RemoveAt(this.previousTargeters.Count - 1);
while (this.Previous.Count(old => targeting.All(actor => actor.ActorId != old.ActorId)) > this.Plugin.Config.NumHistory) {
this.Previous.RemoveAt(this.Previous.Count - 1);
}
this.previousMutex.ReleaseMutex();
this._previousMutex.ReleaseMutex();
}
private Targeter[] GetTargeting(Actor[] actors, Actor player) {
private Targeter[] GetTargeting(IEnumerable<Actor> actors, Actor player) {
return actors
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
.Select(actor => actor as PlayerCharacter)
.Where(actor => this.plugin.Config.LogParty || !InParty(actor))
.Where(actor => this.plugin.Config.LogAlliance || !InAlliance(actor))
.Where(actor => this.plugin.Config.LogInCombat || !InCombat(actor))
.Where(actor => this.plugin.Config.LogSelf || actor.ActorId != player.ActorId)
.Cast<PlayerCharacter>()
.Where(actor => this.Plugin.Config.LogParty || !InParty(actor))
.Where(actor => this.Plugin.Config.LogAlliance || !InAlliance(actor))
.Where(actor => this.Plugin.Config.LogInCombat || !InCombat(actor))
.Where(actor => this.Plugin.Config.LogSelf || actor.ActorId != player.ActorId)
.Select(actor => new Targeter(actor))
.ToArray();
}
private static byte GetStatus(Actor actor) {
IntPtr statusPtr = actor.Address + 0x1906; // updated 5.3
var statusPtr = actor.Address + 0x1906; // updated 5.3
return Marshal.ReadByte(statusPtr);
}
@ -165,54 +166,52 @@ namespace PeepingTom {
private static bool InAlliance(Actor actor) => (GetStatus(actor) & 32) > 0;
private bool CanPlaySound() {
if (!this.plugin.Config.PlaySoundOnTarget) {
if (!this.Plugin.Config.PlaySoundOnTarget) {
return false;
}
if (this.current.Length <= this.lastTargetAmount) {
if (this.Current.Length <= this.LastTargetAmount) {
return false;
}
if (!this.plugin.Config.PlaySoundWhenClosed && !this.plugin.Ui.Visible) {
if (!this.Plugin.Config.PlaySoundWhenClosed && !this.Plugin.Ui.Visible) {
return false;
}
if (this.watch == null) {
this.watch = new Stopwatch();
if (this.Watch == null) {
this.Watch = new Stopwatch();
return true;
}
double secs = this.watch.Elapsed.TotalSeconds;
return secs >= this.plugin.Config.SoundCooldown;
var secs = this.Watch.Elapsed.TotalSeconds;
return secs >= this.Plugin.Config.SoundCooldown;
}
private void PlaySound() {
int soundDevice = this.plugin.Config.SoundDevice;
var soundDevice = this.Plugin.Config.SoundDevice;
if (soundDevice < -1 || soundDevice > WaveOut.DeviceCount) {
soundDevice = -1;
}
new Thread(new ThreadStart(() => {
new Thread(() => {
WaveStream reader;
try {
if (this.plugin.Config.SoundPath == null) {
reader = new WaveFileReader(Properties.Resources.Target);
if (this.Plugin.Config.SoundPath == null) {
reader = new WaveFileReader(Resource.AsStream("Resources/target.wav"));
} else {
reader = new AudioFileReader(this.plugin.Config.SoundPath);
reader = new AudioFileReader(this.Plugin.Config.SoundPath);
}
#pragma warning disable CA1031 // Do not catch general exception types
#pragma warning disable CA1031 // Do not catch general exception types
} catch (Exception e) {
#pragma warning restore CA1031 // Do not catch general exception types
#pragma warning restore CA1031 // Do not catch general exception types
this.SendError($"Could not play sound file: {e.Message}");
return;
}
WaveChannel32 channel = new WaveChannel32(reader) {
Volume = this.plugin.Config.SoundVolume,
};
WaveChannel32 channel = new WaveChannel32(reader) {Volume = this.Plugin.Config.SoundVolume,};
using (reader) {
using (var output = new WaveOutEvent() { DeviceNumber = soundDevice }) {
using (var output = new WaveOutEvent() {DeviceNumber = soundDevice}) {
output.Init(channel);
output.Play();
@ -221,30 +220,29 @@ namespace PeepingTom {
}
}
}
})).Start();
}).Start();
}
private void SendError(string message) {
Payload[] payloads = { new TextPayload($"[{this.plugin.Name}] {message}") };
this.plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {
MessageBytes = new SeString(payloads).Encode(),
Type = XivChatType.ErrorMessage,
});
Payload[] payloads = {
new TextPayload($"[{this.Plugin.Name}] {message}"),
};
this.Plugin.Interface.Framework.Gui.Chat.PrintChat(new XivChatEntry {MessageBytes = new SeString(payloads).Encode(), Type = XivChatType.ErrorMessage,});
}
public void Dispose() {
this.currentMutex.Dispose();
this.previousMutex.Dispose();
this._currentMutex.Dispose();
this._previousMutex.Dispose();
}
}
class TargetThreadData {
public PlayerCharacter localPlayer;
public Actor[] actors;
internal class TargetThreadData {
public PlayerCharacter LocalPlayer { get; }
public Actor[] Actors { get; }
public TargetThreadData(DalamudPluginInterface pi) {
this.localPlayer = pi.ClientState.LocalPlayer;
this.actors = pi.ClientState.Actors.ToArray();
this.LocalPlayer = pi.ClientState.LocalPlayer;
this.Actors = pi.ClientState.Actors.ToArray();
}
}
}

View File

@ -19,7 +19,7 @@ namespace PeepingTom {
this.ActorId = character.ActorId;
}
public PlayerCharacter GetPlayerCharacter(DalamudPluginInterface pi) {
public PlayerCharacter? GetPlayerCharacter(DalamudPluginInterface pi) {
if (pi == null) {
throw new ArgumentNullException(nameof(pi), "DalamudPluginInterface cannot be null");
}

View File

@ -1,11 +1,11 @@
namespace PeepingTom {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords")]
public class Optional<T> {
public bool Present { get; private set; }
private readonly T value;
public class Optional<T> where T : class {
public bool Present { get; }
private readonly T? _value;
public Optional(T value) {
this.value = value;
public Optional(T? value) {
this._value = value;
this.Present = true;
}
@ -13,8 +13,8 @@
this.Present = false;
}
public bool Get(out T value) {
value = this.value;
public bool Get(out T? value) {
value = this._value;
return this.Present;
}
}

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.CodeAnalysis.FxCopAnalyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.CodeAnalysis.VersionCheckAnalyzer" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.CodeQuality.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.NetCore.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="Microsoft.NetFramework.Analyzers" version="2.9.6" targetFramework="net48" developmentDependency="true" />
<package id="NAudio" version="1.10.0" targetFramework="net48" />
</packages>