fix: moving to App wrapper
This commit is contained in:
parent
38d7991097
commit
1a0790ffd4
329
.editorconfig
329
.editorconfig
@ -1,138 +1,225 @@
|
|||||||
|
# Supprimer la ligne ci-dessous si vous voulez hériter les paramètres .editorconfig des répertoires supérieurs
|
||||||
root = true
|
root = true
|
||||||
# top-most EditorConfig file
|
|
||||||
|
|
||||||
[*]
|
# Fichiers C#
|
||||||
charset = utf-8
|
[*.cs]
|
||||||
|
|
||||||
end_of_line = lf
|
#### Options EditorConfig principales ####
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
# 4 space indentation
|
# Indentation et espacement
|
||||||
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
tab_width = 2
|
||||||
|
|
||||||
# disable redundant style warnings
|
# Préférences de nouvelle ligne
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
# Microsoft .NET properties
|
#### Conventions de codage .NET ####
|
||||||
|
|
||||||
|
# Organiser les instructions Using
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
file_header_template = unset
|
||||||
|
|
||||||
|
# Préférences de this. et Me.
|
||||||
|
dotnet_style_qualification_for_event = false
|
||||||
|
dotnet_style_qualification_for_field = false
|
||||||
|
dotnet_style_qualification_for_method = false
|
||||||
|
dotnet_style_qualification_for_property = false
|
||||||
|
|
||||||
|
# Préférences des mots clés de langage par rapport aux types BCL
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||||
|
dotnet_style_predefined_type_for_member_access = true
|
||||||
|
|
||||||
|
# Préférences de parenthèses
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||||
|
|
||||||
|
# Préférences de modificateur
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||||
|
|
||||||
|
# Préférences de niveau expression
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_collection_initializer = true
|
||||||
|
dotnet_style_explicit_tuple_names = true
|
||||||
|
dotnet_style_namespace_match_folder = true
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_object_initializer = true
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true
|
||||||
|
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
|
||||||
|
# Préférences de champ
|
||||||
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
|
# Préférences de paramètre
|
||||||
|
dotnet_code_quality_unused_parameters = all
|
||||||
|
|
||||||
|
# Préférences de suppression
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
|
# Préférences de nouvelle ligne
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||||
|
|
||||||
|
#### Conventions de codage C# ####
|
||||||
|
|
||||||
|
# Préférences de var
|
||||||
|
csharp_style_var_elsewhere = false
|
||||||
|
csharp_style_var_for_built_in_types = false
|
||||||
|
csharp_style_var_when_type_is_apparent = false
|
||||||
|
|
||||||
|
# Membres expression-bodied
|
||||||
|
csharp_style_expression_bodied_accessors = true
|
||||||
|
csharp_style_expression_bodied_constructors = false
|
||||||
|
csharp_style_expression_bodied_indexers = true
|
||||||
|
csharp_style_expression_bodied_lambdas = true
|
||||||
|
csharp_style_expression_bodied_local_functions = false
|
||||||
|
csharp_style_expression_bodied_methods = false
|
||||||
|
csharp_style_expression_bodied_operators = false
|
||||||
|
csharp_style_expression_bodied_properties = true
|
||||||
|
|
||||||
|
# Préférences correspondants au modèle
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||||
|
csharp_style_prefer_extended_property_pattern = true
|
||||||
|
csharp_style_prefer_not_pattern = true
|
||||||
|
csharp_style_prefer_pattern_matching = true
|
||||||
|
csharp_style_prefer_switch_expression = true
|
||||||
|
|
||||||
|
# Préférences de vérification de valeur Null
|
||||||
|
csharp_style_conditional_delegate_call = true
|
||||||
|
|
||||||
|
# Préférences de modificateur
|
||||||
|
csharp_prefer_static_local_function = true
|
||||||
|
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
|
||||||
|
|
||||||
|
# Préférences de bloc de code
|
||||||
|
csharp_prefer_braces = true
|
||||||
|
csharp_prefer_simple_using_statement = true
|
||||||
|
csharp_style_namespace_declarations = block_scoped
|
||||||
|
csharp_style_prefer_method_group_conversion = true
|
||||||
|
csharp_style_prefer_top_level_statements = true
|
||||||
|
|
||||||
|
# Préférences de niveau expression
|
||||||
|
csharp_prefer_simple_default_expression = true
|
||||||
|
csharp_style_deconstructed_variable_declaration = true
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||||
|
csharp_style_inlined_variable_declaration = true
|
||||||
|
csharp_style_prefer_index_operator = true
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true
|
||||||
|
csharp_style_prefer_range_operator = true
|
||||||
|
csharp_style_prefer_tuple_swap = true
|
||||||
|
csharp_style_prefer_utf8_string_literals = true
|
||||||
|
csharp_style_throw_expression = true
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||||
|
|
||||||
|
# Préférences pour la directive 'using'
|
||||||
|
csharp_using_directive_placement = outside_namespace
|
||||||
|
|
||||||
|
# Préférences de nouvelle ligne
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||||
|
|
||||||
|
#### Règles de formatage C# ####
|
||||||
|
|
||||||
|
# Préférences de nouvelle ligne
|
||||||
|
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
|
||||||
|
|
||||||
|
# Préférences de mise en retrait
|
||||||
|
csharp_indent_block_contents = true
|
||||||
csharp_indent_braces = false
|
csharp_indent_braces = false
|
||||||
csharp_new_line_before_catch = true
|
csharp_indent_case_contents = true
|
||||||
csharp_new_line_before_else = true
|
csharp_indent_case_contents_when_block = true
|
||||||
csharp_new_line_before_finally = true
|
csharp_indent_labels = one_less_than_current
|
||||||
csharp_new_line_before_members_in_object_initializers = false
|
csharp_indent_switch_labels = true
|
||||||
csharp_new_line_before_open_brace = all
|
|
||||||
csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
|
# Préférences d'espace
|
||||||
csharp_style_var_elsewhere = true:suggestion
|
csharp_space_after_cast = false
|
||||||
csharp_style_var_for_built_in_types = true:suggestion
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
csharp_space_after_comma = true
|
||||||
dotnet_code_quality_unused_parameters = non_public
|
csharp_space_after_dot = false
|
||||||
dotnet_naming_rule.event_rule.severity = warning
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
dotnet_naming_rule.event_rule.style = on_upper_camel_case_style
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
dotnet_naming_rule.event_rule.symbols = event_symbols
|
csharp_space_around_binary_operators = before_and_after
|
||||||
dotnet_naming_rule.private_constants_rule.severity = warning
|
csharp_space_around_declaration_statements = false
|
||||||
dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
|
csharp_space_before_comma = false
|
||||||
dotnet_naming_rule.private_instance_fields_rule.severity = warning
|
csharp_space_before_dot = false
|
||||||
dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
|
csharp_space_before_open_square_brackets = false
|
||||||
dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
dotnet_naming_rule.private_static_fields_rule.severity = warning
|
csharp_space_between_empty_square_brackets = false
|
||||||
dotnet_naming_rule.private_static_fields_rule.style = upper_camel_case_style
|
|
||||||
dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
|
|
||||||
dotnet_naming_rule.private_static_readonly_rule.severity = warning
|
|
||||||
dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
|
|
||||||
dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
|
|
||||||
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
|
|
||||||
dotnet_naming_style.on_upper_camel_case_style.capitalization = pascal_case
|
|
||||||
dotnet_naming_style.on_upper_camel_case_style.required_prefix = On
|
|
||||||
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
|
|
||||||
dotnet_naming_symbols.event_symbols.applicable_accessibilities = *
|
|
||||||
dotnet_naming_symbols.event_symbols.applicable_kinds = event
|
|
||||||
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
|
|
||||||
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
|
|
||||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
|
|
||||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
|
|
||||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators =always_for_clarity:suggestion
|
|
||||||
dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
|
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
|
||||||
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
|
|
||||||
dotnet_style_object_initializer = false
|
|
||||||
csharp_space_between_method_call_empty_parameter_list_parentheses = 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_call_parameter_list_parentheses = false
|
||||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
csharp_space_between_empty_square_brackets = false
|
|
||||||
csharp_space_before_semicolon_in_for_statement = false
|
|
||||||
csharp_space_before_open_square_brackets = false
|
|
||||||
csharp_space_before_comma = false
|
|
||||||
csharp_space_after_keywords_in_control_flow_statements = true
|
|
||||||
csharp_space_after_comma = true
|
|
||||||
csharp_space_after_cast = false
|
|
||||||
csharp_space_around_binary_operators = before_and_after
|
|
||||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
csharp_space_between_parentheses = none
|
csharp_space_between_parentheses = false
|
||||||
csharp_space_between_square_brackets = false
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
# ReSharper properties
|
# Préférences d'enveloppement
|
||||||
resharper_align_linq_query = true
|
csharp_preserve_single_line_blocks = true
|
||||||
resharper_align_multiline_argument = true
|
csharp_preserve_single_line_statements = true
|
||||||
resharper_align_multiline_calls_chain = true
|
|
||||||
resharper_align_multiline_expression = true
|
|
||||||
resharper_align_multiline_extends_list = true
|
|
||||||
resharper_align_multiline_for_stmt = true
|
|
||||||
resharper_align_multline_type_parameter_constrains = true
|
|
||||||
resharper_align_multline_type_parameter_list = true
|
|
||||||
resharper_apply_on_completion = true
|
|
||||||
resharper_auto_property_can_be_made_get_only_global_highlighting = none
|
|
||||||
resharper_auto_property_can_be_made_get_only_local_highlighting = none
|
|
||||||
resharper_autodetect_indent_settings = true
|
|
||||||
resharper_braces_for_ifelse = required_for_multiline
|
|
||||||
resharper_can_use_global_alias = false
|
|
||||||
resharper_csharp_align_multiline_parameter = true
|
|
||||||
resharper_csharp_align_multiple_declaration = true
|
|
||||||
resharper_csharp_empty_block_style = together_same_line
|
|
||||||
resharper_csharp_int_align_comments = true
|
|
||||||
resharper_csharp_new_line_before_while = true
|
|
||||||
resharper_csharp_wrap_after_declaration_lpar = true
|
|
||||||
resharper_enforce_line_ending_style = true
|
|
||||||
resharper_member_can_be_private_global_highlighting = none
|
|
||||||
resharper_member_can_be_private_local_highlighting = none
|
|
||||||
resharper_new_line_before_finally = false
|
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
|
||||||
resharper_place_field_attribute_on_same_line = false
|
|
||||||
resharper_show_autodetect_configure_formatting_tip = false
|
|
||||||
resharper_use_indent_from_vs = false
|
|
||||||
|
|
||||||
# ReSharper inspection severities
|
#### Styles de nommage ####
|
||||||
resharper_arrange_missing_parentheses_highlighting = hint
|
|
||||||
resharper_arrange_redundant_parentheses_highlighting = hint
|
|
||||||
resharper_arrange_this_qualifier_highlighting = none
|
|
||||||
resharper_arrange_type_member_modifiers_highlighting = hint
|
|
||||||
resharper_arrange_type_modifiers_highlighting = hint
|
|
||||||
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
|
||||||
resharper_built_in_type_reference_style_highlighting = none
|
|
||||||
resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = none
|
|
||||||
resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = none
|
|
||||||
resharper_invert_if_highlighting = none
|
|
||||||
resharper_loop_can_be_converted_to_query_highlighting = none
|
|
||||||
resharper_method_has_async_overload_highlighting = none
|
|
||||||
resharper_private_field_can_be_converted_to_local_variable_highlighting = none
|
|
||||||
resharper_redundant_base_qualifier_highlighting = none
|
|
||||||
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
|
||||||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
|
||||||
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
|
||||||
resharper_unused_auto_property_accessor_global_highlighting = none
|
|
||||||
csharp_style_deconstructed_variable_declaration=true:silent
|
|
||||||
|
|
||||||
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
# Règles de nommage
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
tab_width = 4
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
|
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
|
||||||
|
|
||||||
|
# Spécifications de symboles
|
||||||
|
|
||||||
|
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 =
|
||||||
|
|
||||||
|
# Styles de nommage
|
||||||
|
|
||||||
|
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
|
||||||
|
339
FFXIV_Vibe_Plugin/App/App.cs
Normal file
339
FFXIV_Vibe_Plugin/App/App.cs
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
// Dalamud libs
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Game.Network;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
|
|
||||||
|
// FFXIV_Vibe_Plugin libs
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
using FFXIV_Vibe_Plugin.Triggers;
|
||||||
|
using FFXIV_Vibe_Plugin.Hooks;
|
||||||
|
using FFXIV_Vibe_Plugin.Experimental;
|
||||||
|
using FFXIV_Vibe_Plugin.Migrations;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin {
|
||||||
|
internal class App {
|
||||||
|
private Dalamud.Game.Gui.ChatGui? DalamudChat { get; init; }
|
||||||
|
public Configuration Configuration { get; init; }
|
||||||
|
private GameNetwork GameNetwork { get; init; }
|
||||||
|
private DataManager DataManager { get; init; }
|
||||||
|
private ClientState ClientState { get; init; }
|
||||||
|
private SigScanner Scanner { get; init; }
|
||||||
|
private ObjectTable GameObjects { get; init; }
|
||||||
|
private DalamudPluginInterface PluginInterface { get; init; }
|
||||||
|
|
||||||
|
// Custom variables from Kacie
|
||||||
|
private readonly bool wasInit = false;
|
||||||
|
public readonly string CommandName = "";
|
||||||
|
private readonly string ShortName = "";
|
||||||
|
private bool _firstUpdated = false;
|
||||||
|
private readonly PlayerStats PlayerStats;
|
||||||
|
private PluginUI PluginUi { get; init; }
|
||||||
|
private ConfigurationProfile ConfigurationProfile;
|
||||||
|
private readonly Logger Logger;
|
||||||
|
private readonly ActionEffect hook_ActionEffect;
|
||||||
|
private readonly Device.DevicesController DeviceController;
|
||||||
|
private readonly TriggersController TriggersController;
|
||||||
|
private readonly Patterns Patterns;
|
||||||
|
|
||||||
|
// Experiments
|
||||||
|
private readonly NetworkCapture experiment_networkCapture;
|
||||||
|
|
||||||
|
public App(string commandName, string shortName, GameNetwork gameNetwork, ClientState clientState, DataManager dataManager, Dalamud.Game.Gui.ChatGui? dalamudChat, Configuration configuration, SigScanner scanner, ObjectTable gameObjects, DalamudPluginInterface pluginInterface) {
|
||||||
|
return;
|
||||||
|
this.CommandName = commandName;
|
||||||
|
this.ShortName = shortName;
|
||||||
|
this.GameNetwork = gameNetwork;
|
||||||
|
this.ClientState = clientState;
|
||||||
|
this.DataManager = dataManager;
|
||||||
|
this.DalamudChat = dalamudChat;
|
||||||
|
this.Configuration = configuration;
|
||||||
|
this.GameObjects = gameObjects;
|
||||||
|
this.Scanner = scanner;
|
||||||
|
this.PluginInterface = pluginInterface;
|
||||||
|
if (DalamudChat != null) {
|
||||||
|
DalamudChat.ChatMessage += ChatWasTriggered;
|
||||||
|
}
|
||||||
|
this.Logger = new Logger(this.DalamudChat, ShortName, Logger.LogLevel.VERBOSE);
|
||||||
|
|
||||||
|
// Migrations
|
||||||
|
Migration migration = new(Configuration, Logger);
|
||||||
|
migration.Patch_0_2_0_to_1_0_0_config_profile();
|
||||||
|
|
||||||
|
// Configuration Profile
|
||||||
|
this.ConfigurationProfile = this.Configuration.GetDefaultProfile();
|
||||||
|
|
||||||
|
// Patterns
|
||||||
|
this.Patterns = new Patterns();
|
||||||
|
this.Patterns.SetCustomPatterns(this.ConfigurationProfile.PatternList);
|
||||||
|
|
||||||
|
// Initialize the devices Controller
|
||||||
|
/* TODO: this.DeviceController = new Device.DevicesController(this.Logger, this.Configuration, this.ConfigurationProfile, this.Patterns);*/
|
||||||
|
this.DeviceController = null;
|
||||||
|
if (this.ConfigurationProfile.AUTO_CONNECT) {
|
||||||
|
Thread t = new(delegate () {
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
this.Command_DeviceController_Connect();
|
||||||
|
});
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Hook ActionEffect
|
||||||
|
this.hook_ActionEffect = new(this.DataManager, this.Logger, this.Scanner, clientState, gameObjects);
|
||||||
|
this.hook_ActionEffect.ReceivedEvent += SpellWasTriggered;
|
||||||
|
|
||||||
|
// Init the login event.
|
||||||
|
this.ClientState.Login += this.ClientState_LoginEvent;
|
||||||
|
|
||||||
|
// Initialize player stats monitoring.
|
||||||
|
this.PlayerStats = new PlayerStats(this.Logger, this.ClientState);
|
||||||
|
PlayerStats.Event_CurrentHpChanged += this.PlayerCurrentHPChanged;
|
||||||
|
PlayerStats.Event_MaxHpChanged += this.PlayerCurrentHPChanged;
|
||||||
|
|
||||||
|
// Triggers
|
||||||
|
this.TriggersController = new Triggers.TriggersController(this.Logger, this.PlayerStats, this.ConfigurationProfile);
|
||||||
|
|
||||||
|
// UI
|
||||||
|
this.PluginUi = new PluginUI(this, this.Logger, this.PluginInterface, this.Configuration, this.ConfigurationProfile, this.DeviceController, this.TriggersController, this.Patterns);
|
||||||
|
|
||||||
|
// Experimental
|
||||||
|
this.experiment_networkCapture = new NetworkCapture(this.Logger, this.GameNetwork);
|
||||||
|
|
||||||
|
// Make sure we set the current profile everywhere.
|
||||||
|
this.SetProfile(this.Configuration.CurrentProfileName);
|
||||||
|
|
||||||
|
// Set the init variable
|
||||||
|
this.wasInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
if (!this.wasInit) { return; }
|
||||||
|
this.Logger.Debug("Disposing plugin...");
|
||||||
|
|
||||||
|
// Cleaning device controller.
|
||||||
|
if (this.DeviceController != null) {
|
||||||
|
this.DeviceController.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleaning chat triggers.
|
||||||
|
|
||||||
|
if (DalamudChat != null) {
|
||||||
|
DalamudChat.ChatMessage -= ChatWasTriggered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleaning hooks
|
||||||
|
this.hook_ActionEffect.Dispose();
|
||||||
|
|
||||||
|
// Cleaning experimentations
|
||||||
|
this.experiment_networkCapture.Dispose();
|
||||||
|
|
||||||
|
this.PluginUi.Dispose();
|
||||||
|
this.Logger.Debug("Plugin disposed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetHelp(string command) {
|
||||||
|
string helpMessage = $@"Usage:
|
||||||
|
{command} config
|
||||||
|
{command} connect
|
||||||
|
{command} disconnect
|
||||||
|
{command} send <0-100> # Send vibe intensity to all toys
|
||||||
|
{command} stop
|
||||||
|
";
|
||||||
|
return helpMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void OnCommand(string command, string args) {
|
||||||
|
if (args.Length == 0) {
|
||||||
|
this.DisplayUI();
|
||||||
|
} else {
|
||||||
|
if (args.StartsWith("help")) {
|
||||||
|
this.Logger.Chat(App.GetHelp($"/{ShortName}"));
|
||||||
|
} else if (args.StartsWith("config")) {
|
||||||
|
this.DisplayConfigUI();
|
||||||
|
} else if (args.StartsWith("connect")) {
|
||||||
|
this.Command_DeviceController_Connect();
|
||||||
|
} else if (args.StartsWith("disconnect")) {
|
||||||
|
this.Command_DeviceController_Disconnect();
|
||||||
|
} else if (args.StartsWith("send")) {
|
||||||
|
this.Command_SendIntensity(args);
|
||||||
|
} else if (args.StartsWith("stop")) {
|
||||||
|
this.DeviceController.SendVibeToAll(0);
|
||||||
|
}
|
||||||
|
// Experimental
|
||||||
|
else if (args.StartsWith("exp_network_start")) {
|
||||||
|
this.experiment_networkCapture.StartNetworkCapture();
|
||||||
|
} else if (args.StartsWith("exp_network_stop")) {
|
||||||
|
this.experiment_networkCapture.StopNetworkCapture();
|
||||||
|
} else {
|
||||||
|
this.Logger.Chat($"Unknown subcommand: {args}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void FirstUpdated() {
|
||||||
|
this.Logger.Debug("First updated");
|
||||||
|
if (this.ConfigurationProfile != null && this.ConfigurationProfile.AUTO_OPEN) {
|
||||||
|
this.DisplayUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayUI() {
|
||||||
|
if (this.PluginUi != null) {
|
||||||
|
this.PluginUi.Display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayConfigUI() {
|
||||||
|
this.PluginUi.Display();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawUI() {
|
||||||
|
|
||||||
|
if(this.PluginUi == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.PluginUi.Draw();
|
||||||
|
|
||||||
|
if (this.ClientState.IsLoggedIn) {
|
||||||
|
this.PlayerStats.Update(this.ClientState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger first updated method
|
||||||
|
if (!this._firstUpdated) {
|
||||||
|
this.FirstUpdated();
|
||||||
|
this._firstUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Command_DeviceController_Connect() {
|
||||||
|
if (this.DeviceController == null) {
|
||||||
|
this.Logger.Error("No device controller available to connect.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.ConfigurationProfile != null) {
|
||||||
|
string host = this.ConfigurationProfile.BUTTPLUG_SERVER_HOST;
|
||||||
|
int port = this.ConfigurationProfile.BUTTPLUG_SERVER_PORT;
|
||||||
|
this.DeviceController.Connect(host, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Command_DeviceController_Disconnect() {
|
||||||
|
if (this.DeviceController == null) {
|
||||||
|
this.Logger.Error("No device controller available to disconnect.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.DeviceController.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Command_SendIntensity(string args) {
|
||||||
|
string[] blafuckcsharp;
|
||||||
|
int intensity;
|
||||||
|
try {
|
||||||
|
blafuckcsharp = args.Split(" ", 2);
|
||||||
|
intensity = int.Parse(blafuckcsharp[1]);
|
||||||
|
this.Logger.Chat($"Command Send intensity {intensity}");
|
||||||
|
} catch (Exception e) when (e is FormatException or IndexOutOfRangeException) {
|
||||||
|
this.Logger.Error($"Malformed arguments for send [intensity].", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.DeviceController == null) {
|
||||||
|
this.Logger.Error("No device controller available to send intensity.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.DeviceController.SendVibeToAll(intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************
|
||||||
|
* LISTEN TO EVENTS *
|
||||||
|
************************************/
|
||||||
|
|
||||||
|
private void SpellWasTriggered(object? sender, HookActionEffects_ReceivedEventArgs args) {
|
||||||
|
if (this.TriggersController == null) {
|
||||||
|
this.Logger.Warn("SpellWasTriggered: TriggersController not init yet, ignoring spell...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Structures.Spell spell = args.Spell;
|
||||||
|
if (this.ConfigurationProfile != null && this.ConfigurationProfile.VERBOSE_SPELL) {
|
||||||
|
this.Logger.Debug($"VERBOSE_SPELL: {spell}");
|
||||||
|
}
|
||||||
|
List<Trigger>? triggers = this.TriggersController.CheckTrigger_Spell(spell);
|
||||||
|
foreach (Trigger trigger in triggers) {
|
||||||
|
this.DeviceController.SendTrigger(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChatWasTriggered(XivChatType chatType, uint senderId, ref SeString _sender, ref SeString _message, ref bool isHandled) {
|
||||||
|
if (this.TriggersController == null) {
|
||||||
|
this.Logger.Warn("ChatWasTriggered: TriggersController not init yet, ignoring chat...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string fromPlayerName = _sender.ToString();
|
||||||
|
if (this.ConfigurationProfile != null && this.ConfigurationProfile.VERBOSE_CHAT) {
|
||||||
|
string XivChatTypeName = ((XivChatType)chatType).ToString();
|
||||||
|
this.Logger.Debug($"VERBOSE_CHAT: {fromPlayerName} type={XivChatTypeName}: {_message}");
|
||||||
|
}
|
||||||
|
List<Trigger> triggers = this.TriggersController.CheckTrigger_Chat(chatType, fromPlayerName, _message.TextValue);
|
||||||
|
foreach (Trigger trigger in triggers) {
|
||||||
|
this.DeviceController.SendTrigger(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetProfile(string profileName) {
|
||||||
|
bool result = this.Configuration.SetCurrentProfile(profileName);
|
||||||
|
if (!result) {
|
||||||
|
this.Logger.Warn($"You are trying to use profile {profileName} which can't be found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ConfigurationProfile? configProfileToCheck = this.Configuration.GetProfile(profileName);
|
||||||
|
if (configProfileToCheck != null) {
|
||||||
|
this.ConfigurationProfile = configProfileToCheck;
|
||||||
|
this.PluginUi.SetProfile(this.ConfigurationProfile);
|
||||||
|
this.DeviceController.SetProfile(this.ConfigurationProfile);
|
||||||
|
this.TriggersController.SetProfile(this.ConfigurationProfile);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClientState_LoginEvent(object? send, EventArgs e) {
|
||||||
|
this.PlayerStats.Update(this.ClientState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerCurrentHPChanged(object? send, EventArgs e) {
|
||||||
|
float currentHP = this.PlayerStats.GetCurrentHP();
|
||||||
|
float maxHP = this.PlayerStats.GetMaxHP();
|
||||||
|
|
||||||
|
if (this.TriggersController == null) {
|
||||||
|
this.Logger.Warn("PlayerCurrentHPChanged: TriggersController not init yet, ignoring HP change...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float percentageHP = currentHP / maxHP * 100f;
|
||||||
|
List<Trigger> triggers = TriggersController.CheckTrigger_HPChanged((int)currentHP, (float)percentageHP);
|
||||||
|
this.Logger.Debug($"Player HPChanged {currentHP}/{maxHP} {percentageHP}%");
|
||||||
|
// Overwrites the threshold for every motors
|
||||||
|
foreach (Trigger trigger in triggers) {
|
||||||
|
this.DeviceController.SendTrigger(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
FFXIV_Vibe_Plugin/App/Commons/Helpers.cs
Normal file
50
FFXIV_Vibe_Plugin/App/Commons/Helpers.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Commons {
|
||||||
|
internal class Helpers {
|
||||||
|
|
||||||
|
/** Get number of milliseconds (unix timestamp) */
|
||||||
|
public static int GetUnix() {
|
||||||
|
return (int)DateTimeOffset.Now.ToUnixTimeMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ClampInt(int value, int min, int max) {
|
||||||
|
if(value < min) { return min; } else if( value > max) { return max; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float ClampFloat(float value, float min, float max) {
|
||||||
|
if(value < min) { return min; } else if(value > max) { return max; }
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int ClampIntensity(int intensity, int threshold) {
|
||||||
|
intensity = ClampInt(intensity, 0, 100);
|
||||||
|
return (int)(intensity / (100.0f / threshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if a regexp matches the given text */
|
||||||
|
public static bool RegExpMatch(Logger Logger, string text, string regexp) {
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
if(regexp.Trim() == "") {
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
string patternCheck = String.Concat(@"", regexp);
|
||||||
|
try {
|
||||||
|
System.Text.RegularExpressions.Match m = Regex.Match(text, patternCheck, RegexOptions.IgnoreCase);
|
||||||
|
if(m.Success) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
} catch(Exception) {
|
||||||
|
Logger.Error($"Probably a wrong REGEXP for {regexp}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
FFXIV_Vibe_Plugin/App/Commons/Logger.cs
Normal file
132
FFXIV_Vibe_Plugin/App/Commons/Logger.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Commons {
|
||||||
|
|
||||||
|
internal class Logger {
|
||||||
|
|
||||||
|
// Initialize the Dalamud.Gui system.
|
||||||
|
private readonly Dalamud.Game.Gui.ChatGui? DalamudChatGui;
|
||||||
|
|
||||||
|
// Logger name.
|
||||||
|
private readonly string name = "";
|
||||||
|
|
||||||
|
// Current log level.
|
||||||
|
private readonly LogLevel log_level = LogLevel.DEBUG;
|
||||||
|
|
||||||
|
// The prefix symbol of the log message.
|
||||||
|
private readonly string prefix = ">";
|
||||||
|
|
||||||
|
// Available log levels.
|
||||||
|
public enum LogLevel {
|
||||||
|
VERBOSE, DEBUG, LOG, INFO, WARN, ERROR, FATAL,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
public Logger(Dalamud.Game.Gui.ChatGui? DalamudChatGui, string name, LogLevel log_level) {
|
||||||
|
this.DalamudChatGui = DalamudChatGui;
|
||||||
|
this.name = name;
|
||||||
|
this.log_level = log_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Printing in the chat gui a message. */
|
||||||
|
public void Chat(string msg) {
|
||||||
|
if(DalamudChatGui != null) {
|
||||||
|
string m = this.FormatMessage(LogLevel.LOG, msg);
|
||||||
|
DalamudChatGui.Print(m);
|
||||||
|
} else {
|
||||||
|
Dalamud.Logging.PluginLog.LogError("No gui chat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Printing in the chat gui an error message. */
|
||||||
|
public void ChatError(string msg) {
|
||||||
|
string m = this.FormatMessage(LogLevel.ERROR, msg);
|
||||||
|
DalamudChatGui?.PrintError(m);
|
||||||
|
this.Error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Printing in the chat gui an error message with an exception. */
|
||||||
|
public void ChatError(string msg, Exception e) {
|
||||||
|
string m = this.FormatMessage(LogLevel.ERROR, msg, e);
|
||||||
|
DalamudChatGui?.PrintError(m);
|
||||||
|
this.Error(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'debug' to logs. */
|
||||||
|
public void Verbose(string msg) {
|
||||||
|
if(this.log_level > LogLevel.VERBOSE) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.VERBOSE, msg);
|
||||||
|
Dalamud.Logging.PluginLog.LogVerbose(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'debug' to logs. */
|
||||||
|
public void Debug(string msg) {
|
||||||
|
if(this.log_level > LogLevel.DEBUG) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.DEBUG, msg);
|
||||||
|
Dalamud.Logging.PluginLog.LogDebug(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'log' to logs. */
|
||||||
|
public void Log(string msg) {
|
||||||
|
if(this.log_level > LogLevel.LOG) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.LOG, msg);
|
||||||
|
Dalamud.Logging.PluginLog.Log(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'info' to logs. */
|
||||||
|
public void Info(string msg) {
|
||||||
|
if(this.log_level > LogLevel.INFO) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.INFO, msg);
|
||||||
|
Dalamud.Logging.PluginLog.Information(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'warning' to logs. */
|
||||||
|
public void Warn(string msg) {
|
||||||
|
if(this.log_level > LogLevel.WARN) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.WARN, msg);
|
||||||
|
Dalamud.Logging.PluginLog.Warning(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'error' to logs. */
|
||||||
|
public void Error(string msg) {
|
||||||
|
if(this.log_level > LogLevel.ERROR) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.ERROR, msg);
|
||||||
|
Dalamud.Logging.PluginLog.Error(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'error' to logs with an exception. */
|
||||||
|
public void Error(string msg, Exception e) {
|
||||||
|
if(this.log_level > LogLevel.ERROR) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.ERROR, msg, e);
|
||||||
|
Dalamud.Logging.PluginLog.Error(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'fatal' to logs. */
|
||||||
|
public void Fatal(string msg) {
|
||||||
|
if(this.log_level > LogLevel.FATAL) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.FATAL, msg);
|
||||||
|
Dalamud.Logging.PluginLog.Fatal(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log message as 'fatal' to logs with an exception. */
|
||||||
|
public void Fatal(string msg, Exception e) {
|
||||||
|
if(this.log_level > LogLevel.FATAL) { return; }
|
||||||
|
string m = this.FormatMessage(LogLevel.FATAL, msg, e);
|
||||||
|
Dalamud.Logging.PluginLog.Fatal(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatMessage(LogLevel type, string msg) {
|
||||||
|
return $"{(name != "" ? name + " " : "")}{type} {this.prefix} {msg}";
|
||||||
|
}
|
||||||
|
private string FormatMessage(LogLevel type, string msg, Exception e) {
|
||||||
|
return $"{(name != "" ? name+" " : "")}{type} {this.prefix} {e.Message}\\n{msg}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
251
FFXIV_Vibe_Plugin/App/Commons/OpCodes.cs
Normal file
251
FFXIV_Vibe_Plugin/App/Commons/OpCodes.cs
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin {
|
||||||
|
internal class OpCodes {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Lobby Connection IPC Codes
|
||||||
|
/**
|
||||||
|
* Server IPC Lobby Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ServerLobbyIpcType : ushort {
|
||||||
|
LobbyError = 0x0002,
|
||||||
|
LobbyServiceAccountList = 0x000C,
|
||||||
|
LobbyCharList = 0x000D,
|
||||||
|
LobbyCharCreate = 0x000E,
|
||||||
|
LobbyEnterWorld = 0x000F,
|
||||||
|
LobbyServerList = 0x0015,
|
||||||
|
LobbyRetainerList = 0x0017,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client IPC Lobby Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ClientLobbyIpcType : ushort {
|
||||||
|
ReqCharList = 0x0003,
|
||||||
|
ReqEnterWorld = 0x0004,
|
||||||
|
ClientVersionInfo = 0x0005,
|
||||||
|
|
||||||
|
ReqCharDelete = 0x000A,
|
||||||
|
ReqCharCreate = 0x000B,
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Zone Connection IPC Codes
|
||||||
|
/**
|
||||||
|
* Server IPC Zone Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ServerZoneIpcType : ushort {
|
||||||
|
PlayerSetup = 0x008B, // updated 6.0
|
||||||
|
UpdateHpMpTp = 0x0296,// updated 6.0
|
||||||
|
PlayerStats = 0x038D,// updated 6.0
|
||||||
|
ActorControl = 0x017E,// updated 6.0
|
||||||
|
ActorControlSelf = 0x02E6,// updated 6.0
|
||||||
|
ActorControlTarget = 0x0168,// updated 6.0
|
||||||
|
Playtime = 0x03C3,// updated 6.0
|
||||||
|
Examine = 0x011B,// updated 6.0
|
||||||
|
MarketBoardSearchResult = 0x0201,// updated 6.0
|
||||||
|
MarketBoardItemListingCount = 0x023C,// updated 6.0
|
||||||
|
MarketBoardItemListingHistory = 0x0192,// updated 6.0
|
||||||
|
MarketBoardItemListing = 0x0323,// updated 6.0
|
||||||
|
MarketBoardPurchase = 0x009D,// updated 6.0
|
||||||
|
ActorMove = 0x0235,// updated 6.0
|
||||||
|
ResultDialog = 0x00AF,// updated 6.0
|
||||||
|
RetainerInformation = 0x0129,// updated 6.0
|
||||||
|
NpcSpawn = 0x032E,// updated 6.0
|
||||||
|
ItemMarketBoardInfo = 0x008A,// updated 6.0
|
||||||
|
PlayerSpawn = 0x0133,// updated 6.0
|
||||||
|
ContainerInfo = 0x00EE,// updated 6.0
|
||||||
|
ItemInfo = 0x0173,// updated 6.0
|
||||||
|
UpdateClassInfo = 0x03A5,// updated 6.0
|
||||||
|
ActorCast = 0x0108,// updated 6.0
|
||||||
|
CurrencyCrystalInfo = 0x0258,// updated 6.0
|
||||||
|
InitZone = 0x02C4,// updated 6.0
|
||||||
|
EffectResult = 0x0196,// updated 6.0
|
||||||
|
EventStart = 0x0334,// updated 6.0
|
||||||
|
EventFinish = 0x01B8,// updated 6.0
|
||||||
|
SomeDirectorUnk4 = 0x0164,// updated 6.0
|
||||||
|
UpdateInventorySlot = 0x02B6,// updated 6.0
|
||||||
|
DesynthResult = 0x02D5,// updated 6.0
|
||||||
|
InventoryActionAck = 0x00FC,// updated 6.0
|
||||||
|
InventoryTransaction = 0x008F,// updated 6.0
|
||||||
|
InventoryTransactionFinish = 0x039B,// updated 6.0
|
||||||
|
CFNotify = 0x0317,// updated 6.0
|
||||||
|
PrepareZoning = 0x0090,// updated 6.0
|
||||||
|
ActorSetPos = 0x0199,// updated 6.0
|
||||||
|
PlaceFieldMarker = 0x037D,// updated 6.0
|
||||||
|
PlaceFieldMarkerPreset = 0x01CF,// updated 6.0
|
||||||
|
ObjectSpawn = 0x0319,// updated 6.0
|
||||||
|
Effect = 0x035A,// updated 6.0
|
||||||
|
StatusEffectList = 0x02C5,// updated 6.0
|
||||||
|
ActorGauge = 0x0283,// updated 6.0
|
||||||
|
FreeCompanyInfo = 0x031C,// updated 6.0
|
||||||
|
FreeCompanyDialog = 0x036E,// updated 6.0
|
||||||
|
AirshipTimers = 0x00ED,// updated 6.0
|
||||||
|
SubmarineTimers = 0x00F5,// updated 6.0
|
||||||
|
AirshipStatusList = 0x023F,// updated 6.0
|
||||||
|
AirshipStatus = 0x01E3,// updated 6.0
|
||||||
|
AirshipExplorationResult = 0x00B4,// updated 6.0
|
||||||
|
SubmarineProgressionStatus = 0x030B,// updated 6.0
|
||||||
|
SubmarineStatusList = 0x02F4,// updated 6.0
|
||||||
|
SubmarineExplorationResult = 0x0183,// updated 6.0
|
||||||
|
|
||||||
|
EventPlay = 0x00A5, // Updated 6.0
|
||||||
|
EventPlay4 = 0x022E, // Updated 6.0
|
||||||
|
EventPlay8 = 0x18B, // Updated 6.0
|
||||||
|
EventPlay16 = 0x1F4, // Updated 6.0
|
||||||
|
EventPlay32 = 0x65, // Updated 6.0
|
||||||
|
EventPlay64 = 0x3A8, // Updated 6.0
|
||||||
|
EventPlay128 = 0x16E, // Updated 6.0
|
||||||
|
EventPlay255 = 0x366, // Updated 6.0
|
||||||
|
|
||||||
|
WeatherChange = 0x01FD, // Updated 6.0
|
||||||
|
|
||||||
|
Logout = 0x02EC, // updated 6.0 hotfix
|
||||||
|
|
||||||
|
//HousingWardInfo = 0x012A, // updated 5.58 hotfix
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client IPC Zone Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ClientZoneIpcType : ushort {
|
||||||
|
UpdatePositionHandler = 0x0346,// updated 6.0
|
||||||
|
ClientTrigger = 0x03AC,// updated 6.0
|
||||||
|
ChatHandler = 0x01CC,// updated 6.0
|
||||||
|
SetSearchInfoHandler = 0x03B1,// updated 6.0
|
||||||
|
MarketBoardPurchaseHandler = 0x00DC,// updated 6.0
|
||||||
|
InventoryModifyHandler = 0x00A3,// updated 6.0 (Base offset: 0x00AA)
|
||||||
|
UpdatePositionInstance = 0x0163,// updated 6.0
|
||||||
|
|
||||||
|
//PingHandler = 0x02CD, // updated 5.58 hotfix
|
||||||
|
//InitHandler = 0x01AA, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//FinishLoadingHandler = 0x02DA, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//CFCommenceHandler = 0x0092, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//CFRegisterDuty = 0x03C7, // updated 5.58 hotfix
|
||||||
|
//CFRegisterRoulette = 0x00C2, // updated 5.58 hotfix
|
||||||
|
//PlayTimeHandler = 0x00B0, // updated 5.58 hotfix
|
||||||
|
//LogoutHandler = 0x0178, // updated 5.58 hotfix
|
||||||
|
//CancelLogout = 0x01F9, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//CFDutyInfoHandler = 0x0092, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//SocialReqSendHandler = 0x023A, // updated 5.58 hotfix
|
||||||
|
//CreateCrossWorldLS = 0x0336, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//SocialListHandler = 0x0187, // updated 5.58 hotfix
|
||||||
|
//ReqSearchInfoHandler = 0x022C, // updated 5.58 hotfix
|
||||||
|
//ReqExamineSearchCommentHandler = 0x0315, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqRemovePlayerFromBlacklist = 0x0145, // updated 5.58 hotfix
|
||||||
|
//BlackListHandler = 0x0161, // updated 5.58 hotfix
|
||||||
|
//PlayerSearchHandler = 0x02FF, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//LinkshellListHandler = 0x023B, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//MarketBoardRequestItemListingInfo = 0x0189, // updated 5.58 hotfix
|
||||||
|
//MarketBoardRequestItemListings = 0x0092, // updated 5.58 hotfix
|
||||||
|
//MarketBoardSearch = 0x02F9, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqExamineFcInfo = 0x0136, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//FcInfoReqHandler = 0x0234, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//FreeCompanyUpdateShortMessageHandler = 0x0123, // added 5.0
|
||||||
|
|
||||||
|
//ReqMarketWishList = 0x0306, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqJoinNoviceNetwork = 0x01D5, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqCountdownInitiate = 0x00C2, // updated 5.58 hotfix
|
||||||
|
//ReqCountdownCancel = 0x00E6, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ZoneLineHandler = 0x03CC, // updated 5.58 hotfix
|
||||||
|
//DiscoveryHandler = 0x023A, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
|
||||||
|
//PlaceFieldMarker = 0x02AF, // updated 5.58 hotfix
|
||||||
|
//PlaceFieldMarkerPreset = 0x018E, // updated 5.58 hotfix
|
||||||
|
//SkillHandler = 0x0244, // updated 5.58 hotfix
|
||||||
|
//GMCommand1 = 0x018A, // updated 5.58 hotfix
|
||||||
|
//GMCommand2 = 0x02FD, // updated 5.58 hotfix
|
||||||
|
//AoESkillHandler = 0x01F1, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//InventoryEquipRecommendedItems = 0x0109, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqPlaceHousingItem = 0x0352, // updated 5.58 hotfix
|
||||||
|
//BuildPresetHandler = 0x024E, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//TalkEventHandler = 0x0305, // updated 5.58 hotfix
|
||||||
|
//EmoteEventHandler = 0x03A7, // updated 5.58 hotfix
|
||||||
|
//WithinRangeEventHandler = 0x02EE, // updated 5.58 hotfix
|
||||||
|
//OutOfRangeEventHandler = 0x00EE, // updated 5.58 hotfix
|
||||||
|
//EnterTeriEventHandler = 0x0389, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReturnEventHandler = 0x03B4, // updated 5.58 hotfix
|
||||||
|
//TradeReturnEventHandler = 0x0216, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//LinkshellEventHandler = 0x0239, // updated 5.58 hotfix
|
||||||
|
//LinkshellEventHandler1 = 0x0239, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//ReqEquipDisplayFlagsChange = 0x01F6, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//LandRenameHandler = 0x018C, // updated 5.58 hotfix
|
||||||
|
//HousingUpdateHouseGreeting = 0x02F4, // updated 5.58 hotfix
|
||||||
|
//HousingUpdateObjectPosition = 0x02CB, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//SetSharedEstateSettings = 0x0179, // updated 5.58 hotfix
|
||||||
|
|
||||||
|
//PerformNoteHandler = 0x016E, // updated 5.58 hotfix
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/// Chat Connection IPC Codes
|
||||||
|
/**
|
||||||
|
* Server IPC Chat Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ServerChatIpcType : ushort {
|
||||||
|
//Tell = 0x0064, // updated for sb
|
||||||
|
//TellErrNotFound = 0x0066,
|
||||||
|
|
||||||
|
//FreeCompanyEvent = 0x012C, // added 5.0
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client IPC Chat Type Codes.
|
||||||
|
*/
|
||||||
|
public enum ClientChatIpcType : ushort {
|
||||||
|
//TellReq = 0x0064,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? GetName(ushort opCode) {
|
||||||
|
string? name = "?Unknow?";
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ServerLobbyIpcType), opCode)) {
|
||||||
|
name = "ServerLobbyIpcType-" + Enum.GetName(typeof(OpCodes.ServerLobbyIpcType), opCode);
|
||||||
|
}
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ClientLobbyIpcType), opCode)) {
|
||||||
|
name = "ClientLobbyIpcType-" + Enum.GetName(typeof(OpCodes.ClientLobbyIpcType), opCode);
|
||||||
|
}
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ServerZoneIpcType), opCode)) {
|
||||||
|
name = "ServerZoneIpcType-" + Enum.GetName(typeof(OpCodes.ServerZoneIpcType), opCode);
|
||||||
|
}
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ClientZoneIpcType), opCode)) {
|
||||||
|
name = "ClientZoneIpcType-" + Enum.GetName(typeof(OpCodes.ClientZoneIpcType), opCode);
|
||||||
|
}
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ServerChatIpcType), opCode)) {
|
||||||
|
name = "ServerChatIpcType-" + Enum.GetName(typeof(OpCodes.ServerChatIpcType), opCode);
|
||||||
|
}
|
||||||
|
if(Enum.IsDefined(typeof(OpCodes.ClientChatIpcType), opCode)) {
|
||||||
|
name = "ClientChatIpcType-"+Enum.GetName(typeof(OpCodes.ClientChatIpcType), opCode);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
FFXIV_Vibe_Plugin/App/Commons/Patterns.cs
Normal file
100
FFXIV_Vibe_Plugin/App/Commons/Patterns.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin {
|
||||||
|
|
||||||
|
public class Patterns {
|
||||||
|
private readonly List<Pattern> BuiltinPatterns = new();
|
||||||
|
private List<Pattern> CustomPatterns = new();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pattern is: [intensity]:[duration in ms]
|
||||||
|
*/
|
||||||
|
public Patterns() {
|
||||||
|
this.AddBuiltinPattern(new Pattern("intensity", "100:0")); // Don't change this one.
|
||||||
|
this.AddBuiltinPattern(new Pattern("ramp", "10:150|20:150|30:150|40:150|50:150|60:150|70:150|80:150|90:150|100:250|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("bump", "10:150|20:150|30:150|40:150|50:150|60:150|70:150|80:150|90:150|100:250|50:250|100:500|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("square", "100:800|50:800|0:200|100:1000|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("shake", "100:500|20:200|100:500|80:500|100:200|90:100|100:200|90:200|100:800|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("sos", "100:500|50:200|100:500|50:200|100:500|50:200|100:1000|30:200|100:1000|30:200|100:1000|30:200|100:500|50:200|100:500|50:200|100:500|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("xenoWave", "10:650|15:500|20:400|30:400|45:350|60:300|75:300|95:250|100:200|90:250|75:300|60:300|45:350|30:400|20:400|15:500|10:650|5:750|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("slowVibe", "10:1000|20:1000|10:1000|50:1000|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Poke Release 25s", "100:3000|50:500|60:500|70:500|80:500|90:500|100:3000|50:500|60:500|70:500|80:500|90:500|100:3000|50:500|60:500|70:500|80:500|90:500|100:3000|50:500|60:500|70:500|80:500|90:500|100:3000|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Stop to gentle 14.6s", "40:200|50:200|60:200|70:200|80:200|90:200|100:3000|90:600|80:800|70:1000|60:1200|50:1400|40:1600|30:1800|25:2000|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Teleport 7s", "20:500|30:500|40:500|50:500|60:500|70:500|80:500|90:500|100:1000|90:200|80:200|70:200|60:200|50:200|40:200|30:200|20:200|10:200|5:200|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Paralysis 7s", "100:200|0:200|100:200|0:200|100:200|0:500|100:200|0:200|100:200|0:200|100:200|0:500|100:200|0:200|100:200|0:200|100:200|0:500|200:100|200:0|200:100|200:0|200:100|500:0|200:100|200:0|200:100|200:0|200:100|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Paralysis (longer) 11.3s", "50:200|0:200|100:500|0:200|40:200|0:700|20:200|0:500|100:200|0:200|60:200|0:400|80:200|0:300|90:200|0:200|35:200|0:500|55:200|0:200|40:200|0:700|20:200|0:200|100:900|0:200|60:200|0:200|30:200|0:300|70:200|0:200|100:500|0:200|50:200|0:200|100:400|0:200|100:200|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Simple vibe 12s", "50:2000|100:2000|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:200|100:200|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Impact 1.25s", "100:500|90:100|80:100|70:100|60:100|50:100|40:100|30:100|20:100|10:100|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Skyshard 5.65s", "100:100|20:100|40:100|60:100|20:2500|40:150|20:150|80:150|100:2000|80:150|40:150|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Sprint 20s", "100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|100:400|50:400|400:100|400:50|400:100|400:50|400:100|400:50|400:100|400:50|400:100|400:50|400:100|400:50"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("Vel: Heartbeat (fast) 8.3s", "50:200|0:200|70:200|0:1000|50:200|0:200|70:200|0:1000|50:200|0:200|70:200|0:1000|50:200|0:200|70:200|0:1000|0:0"));
|
||||||
|
this.AddBuiltinPattern(new Pattern("FF Victory Jingle 4.95sec", "20:150|0:150|70:150|0:150|0:500|60:150|0:150|40:150|0:150|0:500|30:150|0:150|80:150|0:150|0:500|100:150|0:150|20:150|0:150|0:500|30:150|0:150|40:150|0:150|0:500|90:150|0:150|80:150|0:150|0:500|20:150|0:150|10:150|0:150|0:500|90:150|0:150|80:150|0:150|0:0"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pattern> GetAllPatterns() {
|
||||||
|
return this.BuiltinPatterns.Concat(this.CustomPatterns).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pattern> GetBuiltinPatterns() {
|
||||||
|
return this.BuiltinPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a copy of the list to avoid error if any modification happens */
|
||||||
|
public List<Pattern> GetCustomPatterns() {
|
||||||
|
List<Pattern> newList = new();
|
||||||
|
foreach(Pattern pattern in this.CustomPatterns) {
|
||||||
|
newList.Add(pattern);
|
||||||
|
}
|
||||||
|
return newList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCustomPatterns(List<Pattern> customPatterns) {
|
||||||
|
this.CustomPatterns = customPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pattern GetPatternById(int index) {
|
||||||
|
return this.GetAllPatterns()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddBuiltinPattern(Pattern pattern) {
|
||||||
|
BuiltinPatterns.Add(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCustomPattern(Pattern pattern) {
|
||||||
|
Pattern? foundPattern = CustomPatterns.FirstOrDefault<Pattern>(p => p.Name == pattern.Name );
|
||||||
|
if(foundPattern != null) {
|
||||||
|
foundPattern.Name = pattern.Name;
|
||||||
|
foundPattern.Value = pattern.Value;
|
||||||
|
} else {
|
||||||
|
CustomPatterns.Add(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveCustomPattern(Pattern pattern) {
|
||||||
|
int index = CustomPatterns.IndexOf(pattern);
|
||||||
|
if(index > -1) {
|
||||||
|
this.CustomPatterns.RemoveAt(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Pattern {
|
||||||
|
public int Index = -1;
|
||||||
|
public string Name = "pattern";
|
||||||
|
public string Value = "10:1000";
|
||||||
|
public Pattern(string name="pattern", string value="10:1000") {
|
||||||
|
this.Name = name;
|
||||||
|
this.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
FFXIV_Vibe_Plugin/App/Commons/Structures.cs
Normal file
130
FFXIV_Vibe_Plugin/App/Commons/Structures.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Commons {
|
||||||
|
internal class Structures {
|
||||||
|
|
||||||
|
public enum ActionEffectType : byte {
|
||||||
|
Any = 0,
|
||||||
|
Miss = 1,
|
||||||
|
FullResist = 2,
|
||||||
|
Damage = 3,
|
||||||
|
Heal = 4,
|
||||||
|
BlockedDamage = 5,
|
||||||
|
ParriedDamage = 6,
|
||||||
|
Invulnerable = 7,
|
||||||
|
NoEffectText = 8,
|
||||||
|
Unknown_0 = 9,
|
||||||
|
MpLoss = 10,
|
||||||
|
MpGain = 11,
|
||||||
|
TpLoss = 12,
|
||||||
|
TpGain = 13,
|
||||||
|
GpGain = 14,
|
||||||
|
ApplyStatusEffectTarget = 15,
|
||||||
|
ApplyStatusEffectSource = 16,
|
||||||
|
StatusNoEffect = 20,
|
||||||
|
Taunt = 24,
|
||||||
|
StartActionCombo = 27,
|
||||||
|
ComboSucceed = 28,
|
||||||
|
Knockback = 33,
|
||||||
|
Mount = 40,
|
||||||
|
MountJapaneseVersion = 240,
|
||||||
|
VFX = 59,
|
||||||
|
Transport = 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unused, should be usefull for HookActionEffects but don't know where this field is.
|
||||||
|
public enum DamageType {
|
||||||
|
Unknown = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Magic = 5,
|
||||||
|
Darkness = 6,
|
||||||
|
Physical = 7,
|
||||||
|
LimitBreak = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Still testing: https://github.com/SapphireServer/Sapphire/blob/master/src/common/Network/PacketDef/Zone/ClientZoneDef.h#L73
|
||||||
|
*/
|
||||||
|
public struct EffectEntry
|
||||||
|
{
|
||||||
|
public ActionEffectType type = ActionEffectType.Any;
|
||||||
|
public byte param0 = 0;
|
||||||
|
public byte param1 = 0;
|
||||||
|
public byte param2 = 0;
|
||||||
|
public byte mult = 0;
|
||||||
|
public byte flags = 0;
|
||||||
|
public ushort value = 0;
|
||||||
|
|
||||||
|
public EffectEntry(ActionEffectType type, byte param0, byte param1, byte param2, byte mult, byte flags, ushort value) {
|
||||||
|
this.type = type;
|
||||||
|
this.param0 = param0;
|
||||||
|
this.param1 = param1;
|
||||||
|
this.param2 = param2;
|
||||||
|
this.mult = mult;
|
||||||
|
this.flags = flags;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"type: {this.type}, p0: {param0}, p1: {param1}, p2: {param2}, mult: {mult}, flags: {flags} | {Convert.ToString(flags, 2)}, value: {value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Player {
|
||||||
|
public int Id;
|
||||||
|
public string Name;
|
||||||
|
public string? Info;
|
||||||
|
public Player(int id, string name, string? info=null) {
|
||||||
|
this.Id = id;
|
||||||
|
this.Name = name;
|
||||||
|
this.Info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
if(this.Info != null) {
|
||||||
|
return $"{Name}({Id}) [info:{this.Info}]";
|
||||||
|
}
|
||||||
|
return $"{Name}({Id})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Spell {
|
||||||
|
public int Id;
|
||||||
|
public string Name = "Undefined_Spell_Name";
|
||||||
|
public Player Player;
|
||||||
|
public int[]? Amounts;
|
||||||
|
public float AmountAverage;
|
||||||
|
public List<Player>? Targets;
|
||||||
|
public DamageType DamageType = 0;
|
||||||
|
public ActionEffectType ActionEffectType = 0;
|
||||||
|
|
||||||
|
public Spell(int id, string name, Player player, int[]? amounts, float amountAverage, List<Player>? targets, DamageType damageType, ActionEffectType actionEffectType) {
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Player = player;
|
||||||
|
Amounts = amounts;
|
||||||
|
AmountAverage = amountAverage;
|
||||||
|
Targets = targets;
|
||||||
|
DamageType = damageType;
|
||||||
|
ActionEffectType = actionEffectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
string targetsString = "";
|
||||||
|
if(Targets != null) {
|
||||||
|
if(Targets.Count > 0) {
|
||||||
|
targetsString = String.Join(",", this.Targets);
|
||||||
|
} else {
|
||||||
|
targetsString = "*no target*";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $"{Player} casts {Name}#{ActionEffectType} on: {targetsString}. Avg: {AmountAverage}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
131
FFXIV_Vibe_Plugin/App/Configuration.cs
Normal file
131
FFXIV_Vibe_Plugin/App/Configuration.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
using Dalamud.Configuration;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Triggers;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin {
|
||||||
|
[Serializable]
|
||||||
|
public class Configuration : IPluginConfiguration {
|
||||||
|
|
||||||
|
public int Version { get; set; } = 0;
|
||||||
|
public string CurrentProfileName = "Default";
|
||||||
|
public List<ConfigurationProfile> Profiles = new();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 2022.01.12
|
||||||
|
* LEGACY from version 2.0.0. Changed to presets in 2.1.0.
|
||||||
|
* This was moved to presets. It should be remove one day */
|
||||||
|
public bool VERBOSE_SPELL = false;
|
||||||
|
public bool VERBOSE_CHAT = false;
|
||||||
|
public bool VIBE_HP_TOGGLE { get; set; } = false;
|
||||||
|
public int VIBE_HP_MODE { get; set; } = 0;
|
||||||
|
public int MAX_VIBE_THRESHOLD { get; set; } = 100;
|
||||||
|
public bool AUTO_CONNECT { get; set; } = true;
|
||||||
|
public bool AUTO_OPEN { get; set; } = false;
|
||||||
|
public List<Pattern> PatternList = new();
|
||||||
|
public string BUTTPLUG_SERVER_HOST { get; set; } = "127.0.0.1";
|
||||||
|
public int BUTTPLUG_SERVER_PORT { get; set; } = 12345;
|
||||||
|
public List<Triggers.Trigger> TRIGGERS { get; set; } = new();
|
||||||
|
public Dictionary<string, FFXIV_Vibe_Plugin.Device.Device> VISITED_DEVICES = new();
|
||||||
|
|
||||||
|
|
||||||
|
// the below exist just to make saving less cumbersome
|
||||||
|
[NonSerialized]
|
||||||
|
private DalamudPluginInterface? pluginInterface;
|
||||||
|
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||||
|
this.pluginInterface = pluginInterface;
|
||||||
|
}
|
||||||
|
public void Save() {
|
||||||
|
this.pluginInterface!.SavePluginConfig(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the profile specified by name.
|
||||||
|
*/
|
||||||
|
public ConfigurationProfile? GetProfile(String name="") {
|
||||||
|
if(name == "") {
|
||||||
|
name = this.CurrentProfileName;
|
||||||
|
}
|
||||||
|
ConfigurationProfile? profile = this.Profiles.Find(i => i.Name == name);
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationProfile GetDefaultProfile() {
|
||||||
|
String defaultProfileName = "Default profile";
|
||||||
|
ConfigurationProfile? profileToCheck = this.GetProfile(this.CurrentProfileName);
|
||||||
|
if(profileToCheck == null) {
|
||||||
|
profileToCheck = this.GetProfile(defaultProfileName);
|
||||||
|
}
|
||||||
|
ConfigurationProfile profileToReturn = profileToCheck ?? (new());
|
||||||
|
if(profileToCheck == null) {
|
||||||
|
profileToReturn.Name = defaultProfileName;
|
||||||
|
this.Profiles.Add(profileToReturn);
|
||||||
|
this.CurrentProfileName = defaultProfileName;
|
||||||
|
this.Save();
|
||||||
|
}
|
||||||
|
return profileToReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationProfile? GetFirstProfile() {
|
||||||
|
ConfigurationProfile? profile = null;
|
||||||
|
if(profile == null && this.Profiles.Count > 0) {
|
||||||
|
profile = this.Profiles[0];
|
||||||
|
}
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveProfile(String name) {
|
||||||
|
ConfigurationProfile? profile = this.GetProfile(name);
|
||||||
|
if(profile != null) {
|
||||||
|
this.Profiles.Remove(profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddProfile(String name) {
|
||||||
|
ConfigurationProfile? profile = GetProfile(name);
|
||||||
|
if(profile == null) {
|
||||||
|
profile = new();
|
||||||
|
profile.Name = name;
|
||||||
|
this.Profiles.Add(profile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetCurrentProfile(String name) {
|
||||||
|
ConfigurationProfile? profile = this.GetProfile(name);
|
||||||
|
if(profile != null) {
|
||||||
|
this.CurrentProfileName = profile.Name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConfigurationProfile{
|
||||||
|
public string Name = "Default";
|
||||||
|
public bool VERBOSE_SPELL = false;
|
||||||
|
public bool VERBOSE_CHAT = false;
|
||||||
|
public bool VIBE_HP_TOGGLE { get; set; } = false;
|
||||||
|
|
||||||
|
public int VIBE_HP_MODE { get; set; } = 0;
|
||||||
|
public int MAX_VIBE_THRESHOLD { get; set; } = 100;
|
||||||
|
public bool AUTO_CONNECT { get; set; } = true;
|
||||||
|
public bool AUTO_OPEN { get; set; } = false;
|
||||||
|
public List<Pattern> PatternList = new();
|
||||||
|
|
||||||
|
public string BUTTPLUG_SERVER_HOST { get; set; } = "127.0.0.1";
|
||||||
|
public int BUTTPLUG_SERVER_PORT { get; set; } = 12345;
|
||||||
|
|
||||||
|
public List<Triggers.Trigger> TRIGGERS { get; set; } = new();
|
||||||
|
|
||||||
|
public Dictionary<string, FFXIV_Vibe_Plugin.Device.Device> VISITED_DEVICES = new();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
BIN
FFXIV_Vibe_Plugin/App/Data/logo.png
Normal file
BIN
FFXIV_Vibe_Plugin/App/Data/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
193
FFXIV_Vibe_Plugin/App/Devices/Device.cs
Normal file
193
FFXIV_Vibe_Plugin/App/Devices/Device.cs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
|
||||||
|
using Buttplug;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Device {
|
||||||
|
public enum UsableCommand {
|
||||||
|
Vibrate,
|
||||||
|
Rotate,
|
||||||
|
Linear,
|
||||||
|
Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Device {
|
||||||
|
private readonly ButtplugClientDevice? ButtplugClientDevice;
|
||||||
|
public int Id = -1;
|
||||||
|
public string Name = "UnsetDevice";
|
||||||
|
public bool CanVibrate = false;
|
||||||
|
public int VibrateMotors = -1;
|
||||||
|
public uint[] VibrateSteps = Array.Empty<uint>();
|
||||||
|
public bool CanRotate = false;
|
||||||
|
public int RotateMotors = -1;
|
||||||
|
public uint[] RotateSteps = Array.Empty<uint>();
|
||||||
|
public bool CanLinear = false;
|
||||||
|
public int LinearMotors = -1;
|
||||||
|
public uint[] LinearSteps = Array.Empty<uint>();
|
||||||
|
public bool CanBattery = false;
|
||||||
|
public bool CanStop = false;
|
||||||
|
public bool IsConnected = false;
|
||||||
|
public double BatteryLevel = -1;
|
||||||
|
// TODO: use that ?
|
||||||
|
public List<UsableCommand> UsableCommands = new();
|
||||||
|
|
||||||
|
public int[] CurrentVibrateIntensity = Array.Empty<int>();
|
||||||
|
public int[] CurrentRotateIntensity = Array.Empty<int>();
|
||||||
|
public int[] CurrentLinearIntensity = Array.Empty<int>();
|
||||||
|
|
||||||
|
public Device(ButtplugClientDevice buttplugClientDevice) {
|
||||||
|
if(buttplugClientDevice != null) {
|
||||||
|
this.ButtplugClientDevice = buttplugClientDevice;
|
||||||
|
Id = (int)buttplugClientDevice.Index;
|
||||||
|
Name = buttplugClientDevice.Name;
|
||||||
|
this.SetCommands();
|
||||||
|
this.ResetMotors();
|
||||||
|
this.UpdateBatteryLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
List<string> commands = this.GetCommandsInfo();
|
||||||
|
return $"Device: {Id}:{Name} (connected={IsConnected}, battery={GetBatteryPercentage()}, commands={String.Join(",", commands)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCommands() {
|
||||||
|
if(this.ButtplugClientDevice == null) { return; }
|
||||||
|
foreach(var cmd in this.ButtplugClientDevice.AllowedMessages) {
|
||||||
|
if(cmd.Key == ServerMessage.Types.MessageAttributeType.VibrateCmd) {
|
||||||
|
this.CanVibrate = true;
|
||||||
|
this.VibrateMotors = (int)cmd.Value.FeatureCount;
|
||||||
|
this.VibrateSteps = cmd.Value.StepCount;
|
||||||
|
this.UsableCommands.Add(UsableCommand.Vibrate);
|
||||||
|
} else if(cmd.Key == ServerMessage.Types.MessageAttributeType.RotateCmd) {
|
||||||
|
this.CanRotate = true;
|
||||||
|
this.RotateMotors = (int)cmd.Value.FeatureCount;
|
||||||
|
this.RotateSteps = cmd.Value.StepCount;
|
||||||
|
this.UsableCommands.Add(UsableCommand.Rotate);
|
||||||
|
} else if(cmd.Key == ServerMessage.Types.MessageAttributeType.LinearCmd) {
|
||||||
|
this.CanLinear = true;
|
||||||
|
this.LinearMotors = (int)cmd.Value.FeatureCount;
|
||||||
|
this.LinearSteps = cmd.Value.StepCount;
|
||||||
|
this.UsableCommands.Add(UsableCommand.Linear);
|
||||||
|
} else if(cmd.Key == ServerMessage.Types.MessageAttributeType.BatteryLevelCmd) {
|
||||||
|
this.CanBattery = true;
|
||||||
|
} else if(cmd.Key == ServerMessage.Types.MessageAttributeType.StopDeviceCmd) {
|
||||||
|
this.CanStop = true;
|
||||||
|
this.UsableCommands.Add(UsableCommand.Stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Init all current motors intensity and default to zero */
|
||||||
|
private void ResetMotors() {
|
||||||
|
if(this.CanVibrate) {
|
||||||
|
this.CurrentVibrateIntensity = new int[this.VibrateMotors];
|
||||||
|
for(int i=0; i<this.VibrateMotors; i++) { this.CurrentVibrateIntensity[i] = 0; };
|
||||||
|
}
|
||||||
|
if(this.CanRotate) {
|
||||||
|
this.CurrentRotateIntensity = new int[this.RotateMotors];
|
||||||
|
for(int i = 0; i < this.RotateMotors; i++) { this.CurrentRotateIntensity[i] = 0; };
|
||||||
|
}
|
||||||
|
if(this.CanLinear) {
|
||||||
|
this.CurrentLinearIntensity = new int[this.LinearMotors];
|
||||||
|
for(int i = 0; i < this.LinearMotors; i++) { this.CurrentLinearIntensity[i] = 0; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UsableCommand> GetUsableCommands() {
|
||||||
|
return this.UsableCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> GetCommandsInfo() {
|
||||||
|
List<string> commands = new();
|
||||||
|
if(CanVibrate) {
|
||||||
|
commands.Add($"vibrate motors={VibrateMotors} steps={String.Join(",", VibrateSteps)}");
|
||||||
|
}
|
||||||
|
if(CanRotate) {
|
||||||
|
commands.Add($"rotate motors={RotateMotors} steps={String.Join(",", RotateSteps)}");
|
||||||
|
}
|
||||||
|
if(CanLinear) {
|
||||||
|
commands.Add($"rotate motors={LinearMotors} steps={String.Join(",", LinearSteps)}");
|
||||||
|
}
|
||||||
|
if(CanBattery) commands.Add("battery");
|
||||||
|
if(CanStop) commands.Add("stop");
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public double UpdateBatteryLevel() {
|
||||||
|
if(this.ButtplugClientDevice == null) { return 0; }
|
||||||
|
if(!CanBattery) {return -1; }
|
||||||
|
Task<double> batteryLevelTask = this.ButtplugClientDevice.SendBatteryLevelCmd();
|
||||||
|
batteryLevelTask.Wait();
|
||||||
|
this.BatteryLevel = batteryLevelTask.Result;
|
||||||
|
return this.BatteryLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetBatteryPercentage() {
|
||||||
|
return $"{this.BatteryLevel*100}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop() {
|
||||||
|
if(this.ButtplugClientDevice == null) { return; }
|
||||||
|
if(CanVibrate) {
|
||||||
|
this.ButtplugClientDevice.SendVibrateCmd(0);
|
||||||
|
}
|
||||||
|
if(CanRotate) {
|
||||||
|
this.ButtplugClientDevice.SendRotateCmd(0f, true);
|
||||||
|
}
|
||||||
|
if(CanStop) {
|
||||||
|
this.ButtplugClientDevice.SendStopDeviceCmd();
|
||||||
|
}
|
||||||
|
ResetMotors();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendVibrate(int intensity, int motorId=-1, int threshold=100) {
|
||||||
|
if(this.ButtplugClientDevice == null) return;
|
||||||
|
if(this.ButtplugClientDevice == null) { return; }
|
||||||
|
if(!CanVibrate || !IsConnected) return;
|
||||||
|
Dictionary<uint, double> motorIntensity = new();
|
||||||
|
for(int i=0; i < this.VibrateMotors; i++) {
|
||||||
|
if(motorId == -1 || motorId == i) {
|
||||||
|
this.CurrentVibrateIntensity[i] = intensity;
|
||||||
|
motorIntensity.Add((uint)i, Helpers.ClampIntensity(intensity, threshold) / 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ButtplugClientDevice.SendVibrateCmd(motorIntensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendRotate(int intensity, bool clockWise=true, int motorId=-1, int threshold = 100) {
|
||||||
|
if(this.ButtplugClientDevice == null) return;
|
||||||
|
if(!CanRotate || !IsConnected) return;
|
||||||
|
Dictionary<uint, (double, bool)> motorIntensity = new();
|
||||||
|
for(int i = 0; i < this.RotateMotors; i++) {
|
||||||
|
if(motorId == -1 || motorId == i) {
|
||||||
|
this.CurrentRotateIntensity[i] = intensity;
|
||||||
|
(double, bool) values = (Helpers.ClampIntensity(intensity, threshold) / 100.0, clockWise);
|
||||||
|
motorIntensity.Add((uint)i, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ButtplugClientDevice.SendRotateCmd(motorIntensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendLinear(int intensity, int duration=500, int motorId = -1, int threshold = 100) {
|
||||||
|
if(this.ButtplugClientDevice == null) return;
|
||||||
|
if(!CanLinear || !IsConnected) return;
|
||||||
|
Dictionary<uint, (uint, double)> motorIntensity = new();
|
||||||
|
for(int i = 0; i < this.LinearMotors; i++) {
|
||||||
|
if(motorId == -1 || motorId == i) {
|
||||||
|
this.CurrentLinearIntensity[i] = intensity;
|
||||||
|
(uint, double) values = ((uint)duration, Helpers.ClampIntensity(intensity, threshold) / 100.0);
|
||||||
|
motorIntensity.Add((uint)i, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ButtplugClientDevice.SendLinearCmd(motorIntensity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
435
FFXIV_Vibe_Plugin/App/Devices/DevicesController.cs
Normal file
435
FFXIV_Vibe_Plugin/App/Devices/DevicesController.cs
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#region FFXIV_Vibe_Plugin deps
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Other deps
|
||||||
|
using Buttplug;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Device {
|
||||||
|
internal class DevicesController {
|
||||||
|
private readonly Logger Logger;
|
||||||
|
private readonly Configuration Configuration;
|
||||||
|
private ConfigurationProfile Profile;
|
||||||
|
private readonly Patterns Patterns;
|
||||||
|
private Triggers.Trigger? CurrentPlayingTrigger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of the current device and motor when it started to play as a unix timestamp.
|
||||||
|
* This is used to detect if a thread that runs a pattern should stop
|
||||||
|
*/
|
||||||
|
private readonly Dictionary<string, int> CurrentDeviceAndMotorPlaying = new();
|
||||||
|
|
||||||
|
// Buttplug related
|
||||||
|
private ButtplugClient? BPClient;
|
||||||
|
private readonly List<Device> Devices = new();
|
||||||
|
private bool isScanning = false;
|
||||||
|
|
||||||
|
// Internal variables
|
||||||
|
private readonly static Mutex mut = new();
|
||||||
|
|
||||||
|
public DevicesController(Logger logger, Configuration configuration, ConfigurationProfile profile, Patterns patterns) {
|
||||||
|
this.Logger = logger;
|
||||||
|
this.Configuration = configuration;
|
||||||
|
this.Profile = profile;
|
||||||
|
this.Patterns = patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
this.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProfile(ConfigurationProfile profile) {
|
||||||
|
this.Profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Connect(String host, int port) {
|
||||||
|
if(this.IsConnected()) {
|
||||||
|
this.Logger.Debug("Disconnecting previous instance! Waiting 2sec...");
|
||||||
|
this.Disconnect();
|
||||||
|
Thread.Sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.BPClient = new("bp-dalamud");
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.Logger.Error($"Can't load bp.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.BPClient.ServerDisconnect += BPClient_ServerDisconnected;
|
||||||
|
this.BPClient.DeviceAdded += BPClient_DeviceAdded;
|
||||||
|
this.BPClient.DeviceRemoved += BPClient_DeviceRemoved;
|
||||||
|
this.BPClient.ScanningFinished += BPClient_OnScanComplete;
|
||||||
|
string hostandport = host + ":" + port.ToString();
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
var uri = new Uri($"ws://{hostandport}/buttplug");
|
||||||
|
var connector = new ButtplugWebsocketConnectorOptions(uri);
|
||||||
|
this.Logger.Log($"Connecting to {hostandport}.");
|
||||||
|
Task task = this.BPClient.ConnectAsync(connector);
|
||||||
|
task.Wait();
|
||||||
|
this.ScanDevice();
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.Logger.Error($"Could not connect to {hostandport}.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(200);
|
||||||
|
|
||||||
|
if(this.BPClient.Connected) {
|
||||||
|
this.Logger.Log($"FVP connected to Intiface!");
|
||||||
|
} else {
|
||||||
|
this.Logger.Error("Failed connecting (Intiface server is up?)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BPClient_ServerDisconnected(object? sender, EventArgs e) {
|
||||||
|
this.Logger.Debug("Server disconnected");
|
||||||
|
this.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected() {
|
||||||
|
bool isConnected = false;
|
||||||
|
if(this.BPClient != null) {
|
||||||
|
isConnected = this.BPClient.Connected;
|
||||||
|
}
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ScanDevice() {
|
||||||
|
if(this.BPClient == null) { return; }
|
||||||
|
this.Logger.Debug("Scanning for devices...");
|
||||||
|
if(this.IsConnected()) {
|
||||||
|
try {
|
||||||
|
this.isScanning = true;
|
||||||
|
var task = this.BPClient.StartScanningAsync();
|
||||||
|
task.Wait();
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.isScanning = false;
|
||||||
|
this.Logger.Error("Scanning issue. No 'Device Comm Managers' enabled on Intiface?");
|
||||||
|
this.Logger.Error(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public bool IsScanning() {
|
||||||
|
return this.isScanning;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopScanningDevice() {
|
||||||
|
if(this.BPClient != null && this.IsConnected()) {
|
||||||
|
try {
|
||||||
|
Task task = this.BPClient.StopScanningAsync();
|
||||||
|
task.Wait();
|
||||||
|
} catch(Exception) {
|
||||||
|
this.Logger.Debug("StopScanningDevice ignored: already stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isScanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BPClient_OnScanComplete(object? sender, EventArgs e) {
|
||||||
|
this.Logger.Debug("Stop scanning...");
|
||||||
|
// FIXME: this is not working, bp client emit the trigger instantly. Let's ignore for the moment.
|
||||||
|
// this.isScanning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BPClient_DeviceAdded(object? sender, DeviceAddedEventArgs arg) {
|
||||||
|
try {
|
||||||
|
mut.WaitOne();
|
||||||
|
ButtplugClientDevice BPClientDevice = arg.Device;
|
||||||
|
Device device = new(BPClientDevice);
|
||||||
|
device.IsConnected = true;
|
||||||
|
this.Logger.Log($"{arg.Device.Name}, {BPClientDevice.Name}");
|
||||||
|
this.Devices.Add(device);
|
||||||
|
if(!this.Profile.VISITED_DEVICES.ContainsKey(device.Name)) {
|
||||||
|
this.Profile.VISITED_DEVICES[device.Name] = device;
|
||||||
|
this.Configuration.Save();
|
||||||
|
this.Logger.Debug($"Adding device to visited list {device})");
|
||||||
|
}
|
||||||
|
this.Logger.Debug($"Added {device})");
|
||||||
|
} finally {
|
||||||
|
mut.ReleaseMutex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BPClient_DeviceRemoved(object? sender, DeviceRemovedEventArgs e) {
|
||||||
|
try {
|
||||||
|
mut.WaitOne();
|
||||||
|
int index = this.Devices.FindIndex(device => device.Id == e.Device.Index);
|
||||||
|
if(index > -1) {
|
||||||
|
this.Logger.Debug($"Removed {Devices[index]}");
|
||||||
|
Device device = Devices[index];
|
||||||
|
this.Devices.RemoveAt(index);
|
||||||
|
device.IsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
mut.ReleaseMutex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disconnect() {
|
||||||
|
this.Devices.Clear();
|
||||||
|
if(this.BPClient == null || !this.IsConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if(this.BPClient.IsScanning) {
|
||||||
|
var task = this.BPClient.StopScanningAsync();
|
||||||
|
task.Wait();
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.Logger.Error("Couldn't stop scanning device... Unknown reason.");
|
||||||
|
this.Logger.Error(e.Message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for(int i = 0; i < this.BPClient.Devices.Length; i++) {
|
||||||
|
this.Logger.Log($"Disconnecting device {i} {this.BPClient.Devices[i].Name}");
|
||||||
|
this.BPClient.Devices[i].Dispose();
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.Logger.Error("Error while disconnecting device", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
if(this.BPClient != null) {
|
||||||
|
this.BPClient.DisconnectAsync();
|
||||||
|
this.Logger.Log("Disconnecting! Bye... Waiting 2sec...");
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
// ignore exception, we are trying to do our best
|
||||||
|
this.Logger.Error("Error while disconnecting client", e);
|
||||||
|
}
|
||||||
|
this.BPClient = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Device> GetDevices() {
|
||||||
|
return this.Devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<String, Device> GetVisitedDevices() {
|
||||||
|
return this.Profile.VISITED_DEVICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAllBatteryLevel() {
|
||||||
|
foreach(Device device in this.GetDevices()) {
|
||||||
|
device.UpdateBatteryLevel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopAll() {
|
||||||
|
foreach(Device device in this.GetDevices()) {
|
||||||
|
device.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendTrigger(Triggers.Trigger trigger, int threshold=100) {
|
||||||
|
if(!this.IsConnected()) {
|
||||||
|
this.Logger.Debug($"Not connected, cannot send ${trigger}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.Logger.Debug($"Sending trigger {trigger} (priority={trigger.Priority})");
|
||||||
|
|
||||||
|
// Check if the trigger has the priority
|
||||||
|
if(this.CurrentPlayingTrigger == null) {
|
||||||
|
this.CurrentPlayingTrigger = trigger;
|
||||||
|
}
|
||||||
|
if(trigger.Priority < this.CurrentPlayingTrigger.Priority) {
|
||||||
|
this.Logger.Debug($"Ignoring trigger because lower priority => {trigger} < {this.CurrentPlayingTrigger}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.CurrentPlayingTrigger = trigger;
|
||||||
|
|
||||||
|
foreach(Triggers.TriggerDevice triggerDevice in trigger.Devices) {
|
||||||
|
Device? device = this.FindDevice(triggerDevice.Name);
|
||||||
|
if(device != null && triggerDevice != null) {
|
||||||
|
|
||||||
|
if(triggerDevice.ShouldVibrate) {
|
||||||
|
for(int motorId = 0; motorId < triggerDevice.VibrateSelectedMotors?.Length; motorId++) {
|
||||||
|
if(triggerDevice.VibrateSelectedMotors != null && triggerDevice.VibrateMotorsThreshold != null) {
|
||||||
|
bool motorEnabled = triggerDevice.VibrateSelectedMotors[motorId];
|
||||||
|
int motorThreshold = triggerDevice.VibrateMotorsThreshold[motorId] * threshold / 100;
|
||||||
|
int motorPatternId = triggerDevice.VibrateMotorsPattern[motorId];
|
||||||
|
float startAfter = trigger.StartAfter;
|
||||||
|
float stopAfter = trigger.StopAfter;
|
||||||
|
if(motorEnabled) {
|
||||||
|
this.Logger.Debug($"Sending {device.Name} vibration to motor: {motorId} patternId={motorPatternId} with threshold: {motorThreshold}!");
|
||||||
|
this.SendPattern("vibrate", device, motorThreshold, motorId, motorPatternId, startAfter, stopAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(triggerDevice.ShouldRotate) {
|
||||||
|
for(int motorId = 0; motorId < triggerDevice.RotateSelectedMotors?.Length; motorId++) {
|
||||||
|
if(triggerDevice.RotateSelectedMotors != null && triggerDevice.RotateMotorsThreshold != null) {
|
||||||
|
bool motorEnabled = triggerDevice.RotateSelectedMotors[motorId];
|
||||||
|
int motorThreshold = triggerDevice.RotateMotorsThreshold[motorId] * threshold / 100;
|
||||||
|
int motorPatternId = triggerDevice.RotateMotorsPattern[motorId];
|
||||||
|
float startAfter = trigger.StartAfter;
|
||||||
|
float stopAfter = trigger.StopAfter;
|
||||||
|
if(motorEnabled) {
|
||||||
|
this.Logger.Debug($"Sending {device.Name} rotation to motor: {motorId} patternId={motorPatternId} with threshold: {motorThreshold}!");
|
||||||
|
this.SendPattern("rotate", device, motorThreshold, motorId, motorPatternId, startAfter, stopAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(triggerDevice.ShouldLinear) {
|
||||||
|
for(int motorId = 0; motorId < triggerDevice.LinearSelectedMotors?.Length; motorId++) {
|
||||||
|
if(triggerDevice.LinearSelectedMotors != null && triggerDevice.LinearMotorsThreshold != null) {
|
||||||
|
bool motorEnabled = triggerDevice.LinearSelectedMotors[motorId];
|
||||||
|
int motorThreshold = triggerDevice.LinearMotorsThreshold[motorId] * threshold / 100;
|
||||||
|
int motorPatternId = triggerDevice.LinearMotorsPattern[motorId];
|
||||||
|
float startAfter = trigger.StartAfter;
|
||||||
|
float stopAfter = trigger.StopAfter;
|
||||||
|
if(motorEnabled) {
|
||||||
|
this.Logger.Debug($"Sending {device.Name} linear to motor: {motorId} patternId={motorPatternId} with threshold: {motorThreshold}!");
|
||||||
|
this.SendPattern("linear", device, motorThreshold, motorId, motorPatternId, startAfter, stopAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(triggerDevice.ShouldStop) {
|
||||||
|
this.Logger.Debug($"Sending stop to {device.Name}!");
|
||||||
|
DevicesController.SendStop(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Search for a device with the corresponding text */
|
||||||
|
public Device? FindDevice(string text) {
|
||||||
|
Device? foundDevice = null;
|
||||||
|
foreach(Device device in this.Devices) {
|
||||||
|
if(device.Name.Contains(text) && device != null) {
|
||||||
|
foundDevice = device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an itensity vibe to all of the devices
|
||||||
|
* @param {float} intensity
|
||||||
|
*/
|
||||||
|
public void SendVibeToAll(int intensity) {
|
||||||
|
if(this.IsConnected() && this.BPClient != null) {
|
||||||
|
foreach(Device device in this.Devices) {
|
||||||
|
device.SendVibrate(intensity, -1, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
device.SendRotate(intensity, true, -1, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
device.SendLinear(intensity, 500, -1, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void SendPattern(string command, Device device, int threshold, int motorId = -1, int patternId = 0, float StartAfter = 0, float StopAfter = 0) {
|
||||||
|
this.SaveCurrentMotorAndDevicePlayingState(device, motorId);
|
||||||
|
Pattern pattern = Patterns.GetPatternById(patternId);
|
||||||
|
|
||||||
|
string[] patternSegments = pattern.Value.Split("|");
|
||||||
|
this.Logger.Log($"SendPattern '{command}' pattern={pattern.Name} ({patternSegments.Length} segments) to {device} motor={motorId} startAfter={StartAfter} stopAfter={StopAfter} threshold={threshold}");
|
||||||
|
|
||||||
|
string deviceAndMotorId = $"{device.Name}:{motorId}";
|
||||||
|
int startedUnixTime = this.CurrentDeviceAndMotorPlaying[deviceAndMotorId];
|
||||||
|
|
||||||
|
// Make sure things stops if StopAfter is set by sending a zero.
|
||||||
|
// We make sure to send the zero to the correct device and if it is still running.
|
||||||
|
bool forceStop = false;
|
||||||
|
Thread tStopAfter = new(delegate () {
|
||||||
|
if(StopAfter == 0) { return; }
|
||||||
|
Thread.Sleep((int)StopAfter * 1000);
|
||||||
|
if(startedUnixTime == this.CurrentDeviceAndMotorPlaying[deviceAndMotorId]) {
|
||||||
|
forceStop = true;
|
||||||
|
this.SendCommand(command, device, 0, motorId);
|
||||||
|
this.Logger.Debug($"Force stopping {deviceAndMotorId} because of StopAfter={StopAfter}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tStopAfter.Start();
|
||||||
|
|
||||||
|
Thread t = new(delegate () {
|
||||||
|
|
||||||
|
Thread.Sleep((int)StartAfter * 1000);
|
||||||
|
|
||||||
|
// Stop exectution if a new pattern is sent to the same device and motor.
|
||||||
|
if(startedUnixTime != this.CurrentDeviceAndMotorPlaying[deviceAndMotorId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Experimental send a fake command to activate connection
|
||||||
|
this.SendCommand(command, device, 0, motorId);
|
||||||
|
Thread.Sleep(50); // Yield if necessary
|
||||||
|
|
||||||
|
for(int segIndex = 0; segIndex < patternSegments.Length; segIndex++) {
|
||||||
|
|
||||||
|
// Stop exectution if a new pattern is send to the same device and motor.
|
||||||
|
if(startedUnixTime != this.CurrentDeviceAndMotorPlaying[deviceAndMotorId]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string patternSegment = patternSegments[segIndex];
|
||||||
|
string[] patternValues = patternSegment.Split(":");
|
||||||
|
int intensity = Helpers.ClampIntensity(Int32.Parse(patternValues[0]), threshold);
|
||||||
|
int duration = Int32.Parse(patternValues[1]);
|
||||||
|
//this.Logger.Debug($"SENDING SEGMENT: intensity={intensity} duration={duration}");
|
||||||
|
|
||||||
|
// Stop after and send 0 intensity
|
||||||
|
if(forceStop || (StopAfter > 0 && StopAfter * 1000 + startedUnixTime < Helpers.GetUnix())) {
|
||||||
|
this.SendCommand(command, device, 0, motorId, duration);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the command \o/
|
||||||
|
this.SendCommand(command, device, intensity, motorId, duration);
|
||||||
|
|
||||||
|
Thread.Sleep(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we clean the current playing trigger.
|
||||||
|
this.CurrentPlayingTrigger = null;
|
||||||
|
});
|
||||||
|
t.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCommand(string command, Device device, int intensity, int motorId, int duration=500) {
|
||||||
|
if(command == "vibrate") {
|
||||||
|
this.SendVibrate(device, intensity, motorId);
|
||||||
|
} else if(command == "rotate") {
|
||||||
|
this.SendRotate(device, intensity, motorId);
|
||||||
|
} else if(command == "linear") {
|
||||||
|
this.SendLinear(device, intensity, motorId, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendVibrate(Device device, int intensity, int motorId = -1) {
|
||||||
|
device.SendVibrate(intensity, motorId, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendRotate(Device device, int intensity, int motorId = -1, bool clockwise = true) {
|
||||||
|
device.SendRotate(intensity, clockwise, motorId, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendLinear(Device device, int intensity, int motorId = -1, int duration = 500) {
|
||||||
|
device.SendLinear(intensity, duration, motorId, this.Profile.MAX_VIBE_THRESHOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendStop(Device device) {
|
||||||
|
device.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveCurrentMotorAndDevicePlayingState(Device device, int motorId) {
|
||||||
|
string deviceAndMotorId = $"{device.Name}:{motorId}";
|
||||||
|
this.CurrentDeviceAndMotorPlaying[deviceAndMotorId] = Helpers.GetUnix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
FFXIV_Vibe_Plugin/App/Experimental/NetworkCapture.cs
Normal file
86
FFXIV_Vibe_Plugin/App/Experimental/NetworkCapture.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Game.Network;
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Experimental {
|
||||||
|
internal class NetworkCapture {
|
||||||
|
|
||||||
|
private readonly Logger Logger;
|
||||||
|
|
||||||
|
// NetworkCapture experiment
|
||||||
|
private readonly GameNetwork? GameNetwork;
|
||||||
|
private bool ExperimentalNetworkCaptureStarted = false;
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
public NetworkCapture(Logger logger, GameNetwork gameNetwork) {
|
||||||
|
this.Logger = logger;
|
||||||
|
this.GameNetwork = gameNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispose all experiments */
|
||||||
|
public void Dispose() {
|
||||||
|
this.StopNetworkCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Monitor the network and caputre some information */
|
||||||
|
public void StartNetworkCapture() {
|
||||||
|
/*
|
||||||
|
this.Logger.Debug("STARTING EXPERIMENTAL");
|
||||||
|
this.ExperimentalNetworkCaptureStarted = true;
|
||||||
|
if(this.GameNetwork != null) {
|
||||||
|
this.GameNetwork.Enable();
|
||||||
|
this.GameNetwork.NetworkMessage += this.OnNetworkReceived;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stops the network capture experiment. */
|
||||||
|
public void StopNetworkCapture() {
|
||||||
|
if(!this.ExperimentalNetworkCaptureStarted) { return; }
|
||||||
|
this.Logger.Debug("STOPPING EXPERIMENTAL");
|
||||||
|
if(this.GameNetwork != null) {
|
||||||
|
this.GameNetwork.NetworkMessage -= this.OnNetworkReceived;
|
||||||
|
}
|
||||||
|
this.ExperimentalNetworkCaptureStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze the network message when received.
|
||||||
|
* 1. We get the opCode
|
||||||
|
* 2. We could retrieve the name of the OpCode using our Common.OpCodes
|
||||||
|
* 3. If it is a ClientTrigger OpCode, we get the correct bytes using Sapphire structs
|
||||||
|
* 4. By analyzing a bit the behavior in the game, we can clearly see that the "param11"
|
||||||
|
* is going from 0 to 1 when the weapon is drawn.
|
||||||
|
*/
|
||||||
|
unsafe private void OnNetworkReceived(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) {
|
||||||
|
int vOut = Convert.ToInt32(opCode);
|
||||||
|
string? name = OpCodes.GetName(opCode);
|
||||||
|
|
||||||
|
uint actionId = 111111111;
|
||||||
|
if(direction == NetworkMessageDirection.ZoneUp) {
|
||||||
|
actionId = *(uint*)(dataPtr + 0x4);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Logger.Log($"Hex: {vOut:X} Decimal: {opCode} ActionId: {actionId} SOURCE_ID: {sourceActorId} TARGET_ID: {targetActorId} DIRECTION: {direction} DATA_PTR: {dataPtr} NAME: {name}");
|
||||||
|
|
||||||
|
if(name == "ClientZoneIpcType-ClientTrigger") {
|
||||||
|
UInt16 commandId = *(UInt16*)(dataPtr);
|
||||||
|
byte unk_1 = *(byte*)(dataPtr + 0x2);
|
||||||
|
byte unk_2 = *(byte*)(dataPtr + 0x3);
|
||||||
|
uint param11 = *(uint*)(dataPtr + 0x4);
|
||||||
|
uint param12 = *(uint*)(dataPtr + 0x8);
|
||||||
|
uint param2 = *(uint*)(dataPtr + 0xC);
|
||||||
|
uint param4 = *(uint*)(dataPtr + 0x10);
|
||||||
|
uint param5 = *(uint*)(dataPtr + 0x14);
|
||||||
|
ulong param3 = *(ulong*)(dataPtr + 0x18);
|
||||||
|
string extra = "";
|
||||||
|
if(param11 == 0) {
|
||||||
|
extra += "WeaponIn";
|
||||||
|
} else if(param11 == 1) {
|
||||||
|
extra += "WeaponOut";
|
||||||
|
}
|
||||||
|
this.Logger.Log($"{name} {direction} {extra} {commandId} {unk_1} {unk_2} {param11} {param12} {param2} {param2} {param4} {param5} {param3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
229
FFXIV_Vibe_Plugin/App/Hooks/ActionEffect.cs
Normal file
229
FFXIV_Vibe_Plugin/App/Hooks/ActionEffect.cs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
#region Dalamud deps
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Data;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region FFXIV_Vibe_Plugin deps
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Hooks {
|
||||||
|
internal class ActionEffect {
|
||||||
|
// Constructor params
|
||||||
|
private readonly DataManager? DataManager;
|
||||||
|
private readonly Logger Logger;
|
||||||
|
private readonly SigScanner Scanner;
|
||||||
|
private readonly ClientState ClientState;
|
||||||
|
private readonly ObjectTable GameObjects;
|
||||||
|
|
||||||
|
// Lumina excel sheet for actions.
|
||||||
|
private readonly Lumina.Excel.ExcelSheet<Lumina.Excel.GeneratedSheets.Action>? LuminaActionSheet;
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
private delegate void HOOK_ReceiveActionEffectDelegate(int sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail);
|
||||||
|
private Hook<HOOK_ReceiveActionEffectDelegate>? receiveActionEffectHook;
|
||||||
|
|
||||||
|
// Event to dispatch.
|
||||||
|
public event EventHandler<HookActionEffects_ReceivedEventArgs>? ReceivedEvent;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
public ActionEffect(DataManager dataManager, Logger logger, SigScanner scanner, ClientState clientState, ObjectTable gameObjects) {
|
||||||
|
this.DataManager = dataManager;
|
||||||
|
this.Logger = logger;
|
||||||
|
this.Scanner = scanner;
|
||||||
|
this.ClientState = clientState;
|
||||||
|
this.GameObjects = gameObjects;
|
||||||
|
this.InitHook();
|
||||||
|
if(DataManager != null) {
|
||||||
|
this.LuminaActionSheet = DataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dispose the hook and disable it */
|
||||||
|
public void Dispose() {
|
||||||
|
receiveActionEffectHook?.Disable();
|
||||||
|
receiveActionEffectHook?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitHook() {
|
||||||
|
try {
|
||||||
|
// Found on: https://github.com/lmcintyre/DamageInfoPlugin/blob/main/DamageInfoPlugin/DamageInfoPlugin.cs#L133
|
||||||
|
IntPtr receiveActionEffectFuncPtr = this.Scanner.ScanText("4C 89 44 24 ?? 55 56 41 54 41 55 41 56");
|
||||||
|
receiveActionEffectHook = new Hook<HOOK_ReceiveActionEffectDelegate>(receiveActionEffectFuncPtr, ReceiveActionEffect);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
this.Dispose();
|
||||||
|
this.Logger.Warn($"Encountered an error loading HookActionEffect: {e.Message}. Disabling it...");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveActionEffectHook.Enable();
|
||||||
|
this.Logger.Log("HookActionEffect was correctly enabled!");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsafe private void ReceiveActionEffect(int sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail) {
|
||||||
|
Structures.Spell spell = new();
|
||||||
|
try {
|
||||||
|
// Get data structure
|
||||||
|
uint ptr_id = *((uint*)effectHeader.ToPointer() + 0x2);
|
||||||
|
uint ptr_animId = *((ushort*)effectHeader.ToPointer() + 0xE);
|
||||||
|
ushort ptr_op = *((ushort*)effectHeader.ToPointer() - 0x7);
|
||||||
|
byte ptr_targetCount = *(byte*)(effectHeader + 0x21);
|
||||||
|
Structures.EffectEntry effect = *(Structures.EffectEntry*)(effectArray);
|
||||||
|
|
||||||
|
// Get more info from data structure
|
||||||
|
string playerName = GetCharacterNameFromSourceId(sourceId);
|
||||||
|
String spellName = this.GetSpellName(ptr_id, true);
|
||||||
|
int[] amounts = this.GetAmounts(ptr_targetCount, effectArray);
|
||||||
|
float amountAverage = ComputeAverageAmount(amounts);
|
||||||
|
List<Structures.Player> targets = this.GetAllTarget(ptr_targetCount, effectTrail, amounts);
|
||||||
|
|
||||||
|
|
||||||
|
// Spell definition
|
||||||
|
spell.Id = (int)ptr_id;
|
||||||
|
spell.Name = spellName;
|
||||||
|
spell.Player = new Structures.Player(sourceId, playerName);
|
||||||
|
spell.Amounts = amounts;
|
||||||
|
spell.AmountAverage = amountAverage;
|
||||||
|
spell.Targets = targets;
|
||||||
|
spell.DamageType = Structures.DamageType.Unknown;
|
||||||
|
|
||||||
|
// WARNING: if there is no target, some information will be wrong !
|
||||||
|
// It is needed to avoid effect type if there is no target.
|
||||||
|
if(targets.Count == 0) {
|
||||||
|
spell.ActionEffectType = Structures.ActionEffectType.Any;
|
||||||
|
} else {
|
||||||
|
spell.ActionEffectType = effect.type;
|
||||||
|
}
|
||||||
|
this.DispatchReceivedEvent(spell);
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.Logger.Log($"{e.Message} {e.StackTrace}");
|
||||||
|
}
|
||||||
|
this.RestoreOriginalHook(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreOriginalHook(int sourceId, IntPtr sourceCharacter, IntPtr pos, IntPtr effectHeader, IntPtr effectArray, IntPtr effectTrail) {
|
||||||
|
if(receiveActionEffectHook != null) {
|
||||||
|
receiveActionEffectHook.Original(sourceId, sourceCharacter, pos, effectHeader, effectArray, effectTrail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe private int[] GetAmounts(byte count, IntPtr effectArray) {
|
||||||
|
int[] RESULT = new int[count];
|
||||||
|
int targetCount = (int)count;
|
||||||
|
int effectsEntries = 0;
|
||||||
|
|
||||||
|
// The packet size depends on the number of target.
|
||||||
|
if(targetCount == 0) {
|
||||||
|
effectsEntries = 0;
|
||||||
|
} else if(targetCount == 1) {
|
||||||
|
effectsEntries = 8;
|
||||||
|
} else if(targetCount <= 8) {
|
||||||
|
effectsEntries = 64;
|
||||||
|
} else if(targetCount <= 16) {
|
||||||
|
effectsEntries = 128;
|
||||||
|
} else if(targetCount <= 24) {
|
||||||
|
effectsEntries = 192;
|
||||||
|
} else if(targetCount <= 32) {
|
||||||
|
effectsEntries = 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a list of EffectEntry (the base binary structure of the effect).
|
||||||
|
List<Structures.EffectEntry> entries = new(effectsEntries);
|
||||||
|
for(int i = 0; i < effectsEntries; i++) {
|
||||||
|
entries.Add(*(Structures.EffectEntry*)(effectArray + i * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum all the damage.
|
||||||
|
int counterValueFound = 0;
|
||||||
|
for(int i = 0; i < entries.Count; i++) {
|
||||||
|
// DEBUG: Logger.Debug(entries[i].ToString());
|
||||||
|
if(i % 8 == 0) { // Value of dmg is located every 8
|
||||||
|
uint tDmg = entries[i].value;
|
||||||
|
if(entries[i].mult != 0) {
|
||||||
|
tDmg += ((uint)ushort.MaxValue + 1) * entries[i].mult;
|
||||||
|
}
|
||||||
|
// We add the value of the damage that we found.
|
||||||
|
if(counterValueFound < count) {
|
||||||
|
RESULT[counterValueFound] = (int)tDmg;
|
||||||
|
}
|
||||||
|
counterValueFound++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ComputeAverageAmount(int[] amounts) {
|
||||||
|
var result = 0;
|
||||||
|
for(int i=0; i < amounts.Length; i++) {
|
||||||
|
result += amounts[i];
|
||||||
|
}
|
||||||
|
result = result != 0 ? result / amounts.Length : result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe private List<Structures.Player> GetAllTarget(byte count, IntPtr effectTrail, int[] amounts) {
|
||||||
|
List<Structures.Player> names = new();
|
||||||
|
if((int)count >= 1) {
|
||||||
|
ulong[] targets = new ulong[(int)count];
|
||||||
|
for(int i=0; i < count; i++) {
|
||||||
|
targets[i] = *(ulong*)(effectTrail + i * 8);
|
||||||
|
var targetId = (int)targets[i];
|
||||||
|
var targetName = this.GetCharacterNameFromSourceId(targetId);
|
||||||
|
var targetPlayer = new Structures.Player(targetId, targetName, $"{amounts[i]}");
|
||||||
|
names.Add(targetPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSpellName(uint actionId, bool withId) {
|
||||||
|
if(this.LuminaActionSheet == null) {
|
||||||
|
this.Logger.Warn("HookActionEffect.GetSpellName: LuminaActionSheet is null");
|
||||||
|
return "***LUMINA ACTION SHEET NOT LOADED***";
|
||||||
|
}
|
||||||
|
var row = this.LuminaActionSheet.GetRow(actionId);
|
||||||
|
var spellName = "";
|
||||||
|
if(row != null) {
|
||||||
|
if(withId) {
|
||||||
|
spellName = $"{row.RowId}:";
|
||||||
|
}
|
||||||
|
if(row.Name != null) {
|
||||||
|
spellName += $"{row.Name}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spellName = "!Unknown Spell Name!";
|
||||||
|
}
|
||||||
|
return spellName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCharacterNameFromSourceId(int sourceId) {
|
||||||
|
var character = this.GameObjects.SearchById((uint)sourceId);
|
||||||
|
var characterName = "";
|
||||||
|
if(character != null) {
|
||||||
|
characterName = character.Name.TextValue;
|
||||||
|
}
|
||||||
|
return characterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DispatchReceivedEvent(Structures.Spell spell) {
|
||||||
|
HookActionEffects_ReceivedEventArgs args = new();
|
||||||
|
args.Spell = spell;
|
||||||
|
ReceivedEvent?.Invoke(this, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventArgs data HookActionEffects_ReceivedEventArgs the 'Received' event is triggers.
|
||||||
|
internal class HookActionEffects_ReceivedEventArgs : EventArgs {
|
||||||
|
public Structures.Spell Spell { get; set; }
|
||||||
|
}
|
||||||
|
}
|
84
FFXIV_Vibe_Plugin/App/PlayerStats.cs
Normal file
84
FFXIV_Vibe_Plugin/App/PlayerStats.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin {
|
||||||
|
|
||||||
|
internal class PlayerStats {
|
||||||
|
private readonly Logger Logger;
|
||||||
|
|
||||||
|
// EVENTS
|
||||||
|
public event EventHandler? Event_CurrentHpChanged;
|
||||||
|
public event EventHandler? Event_MaxHpChanged;
|
||||||
|
|
||||||
|
// Stats of the player
|
||||||
|
private float _CurrentHp, _prevCurrentHp = -1;
|
||||||
|
private float _MaxHp, _prevMaxHp = -1;
|
||||||
|
public string PlayerName = "*unknown*";
|
||||||
|
|
||||||
|
public PlayerStats( Logger logger, ClientState clientState) {
|
||||||
|
this.Logger = logger;
|
||||||
|
this.UpdatePlayerState(clientState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ClientState clientState) {
|
||||||
|
if(clientState == null || clientState.LocalPlayer == null) { return; }
|
||||||
|
this.UpdatePlayerState(clientState);
|
||||||
|
this.UpdatePlayerName(clientState);
|
||||||
|
this.UpdateCurrentHp(clientState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatePlayerState(ClientState clientState) {
|
||||||
|
if(clientState != null && clientState.LocalPlayer != null) {
|
||||||
|
if(this._CurrentHp == -1 || this._MaxHp == -1) {
|
||||||
|
this.Logger.Debug($"UpdatePlayerState {this._CurrentHp} {this._MaxHp}");
|
||||||
|
this._CurrentHp = this._prevCurrentHp = clientState.LocalPlayer.CurrentHp;
|
||||||
|
this._MaxHp = this._prevMaxHp = clientState.LocalPlayer.MaxHp;
|
||||||
|
this.Logger.Debug($"UpdatePlayerState {this._CurrentHp} {this._MaxHp}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UpdatePlayerName(ClientState clientState) {
|
||||||
|
if(clientState != null && clientState.LocalPlayer != null) {
|
||||||
|
this.PlayerName = clientState.LocalPlayer.Name.TextValue;
|
||||||
|
}
|
||||||
|
return this.PlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPlayerName() {
|
||||||
|
return this.PlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCurrentHp(ClientState clientState) {
|
||||||
|
// Updating current values
|
||||||
|
if(clientState != null && clientState.LocalPlayer != null) {
|
||||||
|
this._CurrentHp = clientState.LocalPlayer.CurrentHp;
|
||||||
|
this._MaxHp = clientState.LocalPlayer.MaxHp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send events after all value updated
|
||||||
|
if(this._CurrentHp != this._prevCurrentHp) {
|
||||||
|
Event_CurrentHpChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
if(this._MaxHp != this._prevMaxHp) {
|
||||||
|
Event_MaxHpChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save previous values
|
||||||
|
this._prevCurrentHp = this._CurrentHp;
|
||||||
|
this._prevMaxHp = this._MaxHp;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** PUBLIC API ******/
|
||||||
|
public float GetCurrentHP() {
|
||||||
|
return this._CurrentHp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetMaxHP() {
|
||||||
|
return this._MaxHp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
FFXIV_Vibe_Plugin/App/Triggers/ChatTrigger.cs
Normal file
27
FFXIV_Vibe_Plugin/App/Triggers/ChatTrigger.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Triggers {
|
||||||
|
[Serializable]
|
||||||
|
public class ChatTrigger : IComparable {
|
||||||
|
|
||||||
|
public ChatTrigger(int intensity, string text) {
|
||||||
|
Intensity = intensity;
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Intensity { get; }
|
||||||
|
public string Text { get; }
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"Trigger(intensity: {Intensity}, text: '{Text}')";
|
||||||
|
}
|
||||||
|
public string ToConfigString() {
|
||||||
|
return $"{Intensity} {Text}";
|
||||||
|
}
|
||||||
|
public int CompareTo(object? obj) {
|
||||||
|
int thatintensity = obj is ChatTrigger that ? that.Intensity : 0;
|
||||||
|
return this.Intensity.CompareTo(thatintensity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
131
FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs
Normal file
131
FFXIV_Vibe_Plugin/App/Triggers/Trigger.cs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Device;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Triggers {
|
||||||
|
enum KIND {
|
||||||
|
Chat,
|
||||||
|
Spell,
|
||||||
|
HPChange
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DIRECTION {
|
||||||
|
Any,
|
||||||
|
Outgoing,
|
||||||
|
Incoming,
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Trigger : IComparable<Trigger> {
|
||||||
|
private static readonly int _initAmountMinValue = -1;
|
||||||
|
private static readonly int _initAmountMaxValue = 10000000;
|
||||||
|
|
||||||
|
// General
|
||||||
|
public bool Enabled = true;
|
||||||
|
public int SortOder = -1;
|
||||||
|
public readonly string Id = "";
|
||||||
|
public string Name = "";
|
||||||
|
public string Description = "";
|
||||||
|
public int Kind = (int)KIND.Chat;
|
||||||
|
public int ActionEffectType = (int)FFXIV_Vibe_Plugin.Commons.Structures.ActionEffectType.Any;
|
||||||
|
public int Direction = (int)DIRECTION.Any;
|
||||||
|
public string ChatText = "hello world";
|
||||||
|
public string SpellText = "";
|
||||||
|
public int AmountMinValue = Trigger._initAmountMinValue;
|
||||||
|
public int AmountMaxValue = Trigger._initAmountMaxValue;
|
||||||
|
public bool AmountInPercentage = false;
|
||||||
|
public string FromPlayerName = "";
|
||||||
|
public string ToPlayerName = "";
|
||||||
|
public float StartAfter = 0;
|
||||||
|
public float StopAfter = 0;
|
||||||
|
public int Priority = 0;
|
||||||
|
public readonly List<int> AllowedChatTypes = new ();
|
||||||
|
|
||||||
|
// Devices associated with this trigger
|
||||||
|
public List<TriggerDevice> Devices = new();
|
||||||
|
|
||||||
|
public Trigger(string name) {
|
||||||
|
this.Id = Guid.NewGuid().ToString();
|
||||||
|
this.Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"Trigger(name={this.Name}, id={this.GetShortID()})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Trigger? other) {
|
||||||
|
if(other == null) { return 1; }
|
||||||
|
if(this.SortOder < other.SortOder) {
|
||||||
|
return 1;
|
||||||
|
} else if(this.SortOder > other.SortOder) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetShortID() {
|
||||||
|
return this.Id[..5];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() {
|
||||||
|
this.AmountMaxValue = Trigger._initAmountMaxValue;
|
||||||
|
this.AmountMinValue = Trigger._initAmountMinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TriggerDevice {
|
||||||
|
public string Name = "";
|
||||||
|
public bool IsEnabled = false;
|
||||||
|
|
||||||
|
public bool ShouldVibrate = false;
|
||||||
|
public bool ShouldRotate = false;
|
||||||
|
public bool ShouldLinear = false;
|
||||||
|
public bool ShouldStop = false;
|
||||||
|
|
||||||
|
public Device.Device? Device;
|
||||||
|
|
||||||
|
// Vibrate states per motor
|
||||||
|
public bool[] VibrateSelectedMotors;
|
||||||
|
public int[] VibrateMotorsThreshold;
|
||||||
|
public int[] VibrateMotorsPattern;
|
||||||
|
|
||||||
|
// Rotate states per motor
|
||||||
|
public bool[] RotateSelectedMotors;
|
||||||
|
public int[] RotateMotorsThreshold;
|
||||||
|
public int[] RotateMotorsPattern;
|
||||||
|
|
||||||
|
// Linear states per motor
|
||||||
|
public bool[] LinearSelectedMotors;
|
||||||
|
public int[] LinearMotorsThreshold;
|
||||||
|
public int[] LinearMotorsPattern;
|
||||||
|
|
||||||
|
public TriggerDevice(Device.Device device) {
|
||||||
|
this.Name = device.Name;
|
||||||
|
this.Device = device;
|
||||||
|
|
||||||
|
// Init vibration array
|
||||||
|
this.VibrateSelectedMotors = new bool[device.CanVibrate ? device.VibrateMotors : 0];
|
||||||
|
this.VibrateMotorsThreshold = new int[device.CanVibrate ? device.VibrateMotors : 0];
|
||||||
|
this.VibrateMotorsPattern = new int[device.CanVibrate ? device.VibrateMotors : 0];
|
||||||
|
|
||||||
|
// Init rotate array
|
||||||
|
this.RotateSelectedMotors = new bool[device.CanRotate ? device.RotateMotors : 0];
|
||||||
|
this.RotateMotorsThreshold = new int[device.CanRotate ? device.RotateMotors : 0];
|
||||||
|
this.RotateMotorsPattern = new int[device.CanRotate ? device.RotateMotors : 0];
|
||||||
|
|
||||||
|
// Init linear array
|
||||||
|
this.LinearSelectedMotors = new bool[device.CanLinear ? device.LinearMotors : 0];
|
||||||
|
this.LinearMotorsThreshold = new int[device.CanLinear ? device.LinearMotors : 0];
|
||||||
|
this.LinearMotorsPattern = new int[device.CanLinear ? device.LinearMotors : 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() {
|
||||||
|
return $"TRIGGER_DEVICE {this.Name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
FFXIV_Vibe_Plugin/App/Triggers/TriggersController.cs
Normal file
158
FFXIV_Vibe_Plugin/App/Triggers/TriggersController.cs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Triggers;
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Triggers {
|
||||||
|
internal class TriggersController {
|
||||||
|
private readonly Logger Logger;
|
||||||
|
private readonly PlayerStats PlayerStats;
|
||||||
|
private ConfigurationProfile Profile;
|
||||||
|
private List<Triggers.Trigger> Triggers = new();
|
||||||
|
|
||||||
|
public TriggersController(Logger logger, PlayerStats playerStats, ConfigurationProfile profile) {
|
||||||
|
this.Logger = logger;
|
||||||
|
this.PlayerStats = playerStats;
|
||||||
|
this.Profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProfile(ConfigurationProfile profile) {
|
||||||
|
this.Profile = profile;
|
||||||
|
this.Triggers = profile.TRIGGERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Triggers.Trigger> GetTriggers() {
|
||||||
|
return this.Triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTrigger(Trigger trigger) {
|
||||||
|
this.Triggers.Add(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveTrigger(Trigger trigger) {
|
||||||
|
this.Triggers.Remove(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Trigger> CheckTrigger_Chat(XivChatType chatType, string ChatFromPlayerName, string ChatMsg) {
|
||||||
|
List<Trigger> triggers = new();
|
||||||
|
ChatFromPlayerName = ChatFromPlayerName.Trim().ToLower();
|
||||||
|
for(int triggerIndex = 0; triggerIndex < this.Triggers.Count; triggerIndex++) {
|
||||||
|
Trigger trigger = this.Triggers[triggerIndex];
|
||||||
|
|
||||||
|
// Ignore if not enabled
|
||||||
|
if(!trigger.Enabled) { continue; }
|
||||||
|
|
||||||
|
// Ignore if the player name is not authorized when chat type is not "Echo"
|
||||||
|
if(chatType != XivChatType.Echo) {
|
||||||
|
if(!Helpers.RegExpMatch(this.Logger, ChatFromPlayerName, trigger.FromPlayerName)) { continue; }
|
||||||
|
if(trigger.AllowedChatTypes.Count > 0 && !trigger.AllowedChatTypes.Any(ct => ct == (int)chatType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the KIND of the trigger is a chat and if it matches
|
||||||
|
if(trigger.Kind == (int)KIND.Chat) {
|
||||||
|
if(Helpers.RegExpMatch(this.Logger, ChatMsg, trigger.ChatText)){
|
||||||
|
if(this.Profile.VERBOSE_CHAT) {
|
||||||
|
this.Logger.Debug($"ChatTrigger matched {trigger.ChatText}<>{ChatMsg}, adding {trigger}");
|
||||||
|
}
|
||||||
|
triggers.Add(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Trigger> CheckTrigger_Spell(Structures.Spell spell) {
|
||||||
|
List<Trigger> triggers = new();
|
||||||
|
string spellName = spell.Name != null ? spell.Name.Trim() : "";
|
||||||
|
for(int triggerIndex = 0; triggerIndex < this.Triggers.Count; triggerIndex++) {
|
||||||
|
Trigger trigger = this.Triggers[triggerIndex];
|
||||||
|
|
||||||
|
// Ignore if not enabled
|
||||||
|
if(!trigger.Enabled) { continue; }
|
||||||
|
|
||||||
|
// Ignore if the player name is not authorized
|
||||||
|
if(!Helpers.RegExpMatch(this.Logger, spell.Player.Name, trigger.FromPlayerName)) { continue; }
|
||||||
|
|
||||||
|
if(trigger.Kind == (int)KIND.Spell) {
|
||||||
|
|
||||||
|
if(!Helpers.RegExpMatch(this.Logger, spellName, trigger.SpellText)) { continue; }
|
||||||
|
|
||||||
|
if(trigger.ActionEffectType != (int)Structures.ActionEffectType.Any && trigger.ActionEffectType != (int)spell.ActionEffectType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(trigger.ActionEffectType == (int)Structures.ActionEffectType.Damage || trigger.ActionEffectType == (int)Structures.ActionEffectType.Heal) {
|
||||||
|
if(trigger.AmountMinValue >= spell.AmountAverage) { continue; }
|
||||||
|
if(trigger.AmountMaxValue <= spell.AmountAverage) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
FFXIV_Vibe_Plugin.Triggers.DIRECTION direction = this.GetSpellDirection(spell);
|
||||||
|
|
||||||
|
if(trigger.Direction != (int)FFXIV_Vibe_Plugin.Triggers.DIRECTION.Any && (int)direction != trigger.Direction) { continue;}
|
||||||
|
if(this.Profile.VERBOSE_SPELL) {
|
||||||
|
this.Logger.Debug($"SpellTrigger matched {spell}, adding {trigger}");
|
||||||
|
}
|
||||||
|
triggers.Add(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Trigger> CheckTrigger_HPChanged(int currentHP, float percentageHP) {
|
||||||
|
List<Trigger> triggers = new();
|
||||||
|
|
||||||
|
for(int triggerIndex = 0; triggerIndex < this.Triggers.Count; triggerIndex++) {
|
||||||
|
Trigger trigger = this.Triggers[triggerIndex];
|
||||||
|
|
||||||
|
// Ignore if not enabled
|
||||||
|
if(!trigger.Enabled) { continue; }
|
||||||
|
|
||||||
|
// Check if the amount is in percentage
|
||||||
|
if (trigger.AmountInPercentage) {
|
||||||
|
if (percentageHP < trigger.AmountMinValue) { continue; }
|
||||||
|
if (percentageHP > trigger.AmountMaxValue) { continue; }
|
||||||
|
this.Logger.Debug($"{percentageHP}, {trigger.AmountMinValue}, {trigger.AmountMaxValue}");
|
||||||
|
}
|
||||||
|
// If the amount is not in percentage check the amount value
|
||||||
|
else {
|
||||||
|
if (trigger.AmountMinValue >= currentHP) { continue; }
|
||||||
|
if (trigger.AmountMaxValue <= currentHP) { continue; }
|
||||||
|
}
|
||||||
|
if(trigger.Kind == (int)KIND.HPChange) {
|
||||||
|
triggers.Add(trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FFXIV_Vibe_Plugin.Triggers.DIRECTION GetSpellDirection(Structures.Spell spell) {
|
||||||
|
string myName = this.PlayerStats.GetPlayerName();
|
||||||
|
|
||||||
|
List<Structures.Player> targets = new();
|
||||||
|
if(spell.Targets != null) {
|
||||||
|
targets = spell.Targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(targets.Count >= 1 && targets[0].Name != myName) {
|
||||||
|
return FFXIV_Vibe_Plugin.Triggers.DIRECTION.Outgoing;
|
||||||
|
}
|
||||||
|
if(spell.Player.Name != myName) {
|
||||||
|
return FFXIV_Vibe_Plugin.Triggers.DIRECTION.Incoming;
|
||||||
|
}
|
||||||
|
return FFXIV_Vibe_Plugin.Triggers.DIRECTION.Self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
26
FFXIV_Vibe_Plugin/App/UI/Components/ButtonLink.cs
Normal file
26
FFXIV_Vibe_Plugin/App/UI/Components/ButtonLink.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using ImGuiNET;
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.UI.Components {
|
||||||
|
internal class ButtonLink {
|
||||||
|
|
||||||
|
public static void Draw(string text, string link, Dalamud.Interface.FontAwesomeIcon Icon, Logger Logger) {
|
||||||
|
if(ImGuiComponents.IconButton(Icon)) {
|
||||||
|
try {
|
||||||
|
_ = Process.Start(new ProcessStartInfo() {
|
||||||
|
FileName = link,
|
||||||
|
UseShellExecute = true,
|
||||||
|
});
|
||||||
|
} catch(Exception e) {
|
||||||
|
Logger.Error($"Could not open repoUrl: {link}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ImGui.IsItemHovered()) { ImGui.SetTooltip(text); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1128
FFXIV_Vibe_Plugin/App/UI/PluginUI.cs
Normal file
1128
FFXIV_Vibe_Plugin/App/UI/PluginUI.cs
Normal file
File diff suppressed because it is too large
Load Diff
40
FFXIV_Vibe_Plugin/App/UI/UIBanner.cs
Normal file
40
FFXIV_Vibe_Plugin/App/UI/UIBanner.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
using FFXIV_Vibe_Plugin.Device;
|
||||||
|
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.UI {
|
||||||
|
internal class UIBanner {
|
||||||
|
public static void Draw(int frameCounter, Logger logger, ImGuiScene.TextureWrap image, String donationLink, DevicesController devicesController) {
|
||||||
|
ImGui.Columns(2, "###main_header", false);
|
||||||
|
float logoScale = 0.2f;
|
||||||
|
ImGui.SetColumnWidth(0, (int)(image.Width * logoScale + 20));
|
||||||
|
ImGui.Image(image.ImGuiHandle, new Vector2(image.Width * logoScale, image.Height * logoScale));
|
||||||
|
ImGui.NextColumn();
|
||||||
|
if(devicesController.IsConnected()) {
|
||||||
|
int nbrDevices = devicesController.GetDevices().Count;
|
||||||
|
ImGui.TextColored(ImGuiColors.ParsedGreen, "Your are connected!");
|
||||||
|
ImGui.Text($"Number of device(s): {nbrDevices}");
|
||||||
|
} else {
|
||||||
|
ImGui.TextColored(ImGuiColors.ParsedGrey, "Your are not connected!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(frameCounter < 200) { // Make blink effect
|
||||||
|
ImGui.Text("Donations: ");
|
||||||
|
} else {
|
||||||
|
ImGui.Text(" ");
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text($"{donationLink}");
|
||||||
|
ImGui.SameLine();
|
||||||
|
UI.Components.ButtonLink.Draw("Thanks for the donation ;)", donationLink, Dalamud.Interface.FontAwesomeIcon.Pray, logger);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
FFXIV_Vibe_Plugin/App/UI/UIConnect.cs
Normal file
61
FFXIV_Vibe_Plugin/App/UI/UIConnect.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
using FFXIV_Vibe_Plugin.Device;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.UI {
|
||||||
|
internal class UIConnect {
|
||||||
|
|
||||||
|
public static void Draw(Configuration configuration, ConfigurationProfile configurationProfile, App plugin, DevicesController devicesController) {
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudViolet, "Server address & port");
|
||||||
|
ImGui.BeginChild("###Server", new Vector2(-1, 40f), true);
|
||||||
|
{
|
||||||
|
|
||||||
|
// Connect/disconnect button
|
||||||
|
string config_BUTTPLUG_SERVER_HOST = configurationProfile.BUTTPLUG_SERVER_HOST;
|
||||||
|
ImGui.SetNextItemWidth(200);
|
||||||
|
if(ImGui.InputText("##serverHost", ref config_BUTTPLUG_SERVER_HOST, 99)) {
|
||||||
|
configurationProfile.BUTTPLUG_SERVER_HOST = config_BUTTPLUG_SERVER_HOST.Trim().ToLower();
|
||||||
|
configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
int config_BUTTPLUG_SERVER_PORT = configurationProfile.BUTTPLUG_SERVER_PORT;
|
||||||
|
ImGui.SetNextItemWidth(100);
|
||||||
|
if(ImGui.InputInt("##serverPort", ref config_BUTTPLUG_SERVER_PORT, 10)) {
|
||||||
|
configurationProfile.BUTTPLUG_SERVER_PORT = config_BUTTPLUG_SERVER_PORT;
|
||||||
|
configuration.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.BeginChild("###Main_Connection", new Vector2(-1, 40f), true);
|
||||||
|
{
|
||||||
|
if(!devicesController.IsConnected()) {
|
||||||
|
if(ImGui.Button("Connect", new Vector2(100, 24))) {
|
||||||
|
plugin.Command_DeviceController_Connect();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(ImGui.Button("Disconnect", new Vector2(100, 24))) {
|
||||||
|
devicesController.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox AUTO_CONNECT
|
||||||
|
ImGui.SameLine();
|
||||||
|
bool config_AUTO_CONNECT = configurationProfile.AUTO_CONNECT;
|
||||||
|
if(ImGui.Checkbox("Automatically connects. ", ref config_AUTO_CONNECT)) {
|
||||||
|
configurationProfile.AUTO_CONNECT = config_AUTO_CONNECT;
|
||||||
|
configuration.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using FFXIV_Vibe_Plugin.Commons;
|
||||||
|
|
||||||
|
namespace FFXIV_Vibe_Plugin.Migrations {
|
||||||
|
internal class Migration {
|
||||||
|
private readonly Configuration configuration;
|
||||||
|
private readonly Logger logger;
|
||||||
|
|
||||||
|
public Migration(Configuration configuration, Logger logger) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Patch_0_2_0_to_1_0_0_config_profile() {
|
||||||
|
var VersionToApply = 0;
|
||||||
|
var configuration = this.configuration;
|
||||||
|
var logger = this.logger;
|
||||||
|
if(configuration.Version == VersionToApply && configuration != null) {
|
||||||
|
ConfigurationProfile preset = new() {
|
||||||
|
Name = "Default (auto-migration from v0.2.0 to v1.0.0)",
|
||||||
|
VERBOSE_SPELL = configuration.VERBOSE_SPELL,
|
||||||
|
VERBOSE_CHAT = configuration.VERBOSE_CHAT,
|
||||||
|
VIBE_HP_TOGGLE = configuration.VIBE_HP_TOGGLE,
|
||||||
|
|
||||||
|
VIBE_HP_MODE = configuration.VIBE_HP_MODE,
|
||||||
|
MAX_VIBE_THRESHOLD = configuration.MAX_VIBE_THRESHOLD,
|
||||||
|
AUTO_CONNECT = configuration.AUTO_CONNECT,
|
||||||
|
AUTO_OPEN = configuration.AUTO_OPEN,
|
||||||
|
PatternList = configuration.PatternList,
|
||||||
|
BUTTPLUG_SERVER_HOST = configuration.BUTTPLUG_SERVER_HOST,
|
||||||
|
BUTTPLUG_SERVER_PORT = configuration.BUTTPLUG_SERVER_PORT,
|
||||||
|
TRIGGERS = configuration.TRIGGERS,
|
||||||
|
VISITED_DEVICES = configuration.VISITED_DEVICES
|
||||||
|
};
|
||||||
|
|
||||||
|
configuration.Version = VersionToApply+1;
|
||||||
|
configuration.CurrentProfileName = preset.Name;
|
||||||
|
configuration.Profiles.Add(preset);
|
||||||
|
configuration.Save();
|
||||||
|
logger.Warn("Migration from 2.0.0 to 2.1.0 using profiles done successfully");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
using Dalamud.Configuration;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFXIV_Plugin_Vibe
|
|
||||||
{
|
|
||||||
[Serializable]
|
|
||||||
public class Configuration : IPluginConfiguration
|
|
||||||
{
|
|
||||||
public int Version { get; set; } = 0;
|
|
||||||
|
|
||||||
public bool SomePropertyToBeSavedAndWithADefault { get; set; } = true;
|
|
||||||
|
|
||||||
// the below exist just to make saving less cumbersome
|
|
||||||
[NonSerialized]
|
|
||||||
private DalamudPluginInterface? PluginInterface;
|
|
||||||
|
|
||||||
public void Initialize(DalamudPluginInterface pluginInterface)
|
|
||||||
{
|
|
||||||
this.PluginInterface = pluginInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
this.PluginInterface!.SavePluginConfig(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,7 +21,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\Data\goat.png">
|
<Content Include="..\Data\logo.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Visible>false</Visible>
|
<Visible>false</Visible>
|
||||||
</Content>
|
</Content>
|
||||||
@ -37,6 +37,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Buttplug" Version="2.0.6" />
|
||||||
<PackageReference Include="DalamudPackager" Version="2.1.10" />
|
<PackageReference Include="DalamudPackager" Version="2.1.10" />
|
||||||
<Reference Include="FFXIVClientStructs">
|
<Reference Include="FFXIVClientStructs">
|
||||||
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"Author": "Kaciexx",
|
"Author": "Kaciexx",
|
||||||
"Name": "FFXIV Vibe Plugin",
|
"Name": "FFXIV Vibe Plugin",
|
||||||
"Punchline": "Vibe controllers or toys",
|
"Punchline": "Vibe controllers or toys",
|
||||||
"Description": "Plugin that let you vibe your controller or toys",
|
"Description": "Plugin that let you vibe your controller or toys",
|
||||||
"RepoUrl": "https://github.com/kaciexx/FFXIV_Vibe_Plugin",
|
"RepoUrl": "https://github.com/kaciexx/FFXIV_Vibe_Plugin",
|
||||||
"InternalName": "FFXIV_Vibe_Plugin",
|
"InternalName": "FFXIV_Vibe_Plugin",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"Tags": [
|
"Tags": [
|
||||||
"vibe",
|
"vibe",
|
||||||
"vibration",
|
"vibration",
|
||||||
"wave"
|
"wave"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,69 +1,87 @@
|
|||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Network;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using FFXIV_Vibe_Plugin.Windows;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using FFXIV_Plugin_Vibe.Windows;
|
|
||||||
|
|
||||||
namespace FFXIV_Plugin_Vibe
|
namespace FFXIV_Vibe_Plugin {
|
||||||
{
|
public sealed class Plugin : IDalamudPlugin {
|
||||||
public sealed class Plugin : IDalamudPlugin
|
// Dalamud plugin definition
|
||||||
{
|
public string Name => "FFXIV Vibe Plugin";
|
||||||
public string Name => "Sample Plugin";
|
public static readonly string ShortName = "FVP";
|
||||||
private const string CommandName = "/pmycommand";
|
public readonly string CommandName = "/fvp";
|
||||||
|
|
||||||
private DalamudPluginInterface PluginInterface { get; init; }
|
// Dalamud plugins
|
||||||
private CommandManager CommandManager { get; init; }
|
private Dalamud.Game.Gui.ChatGui? DalamudChat { get; init; }
|
||||||
public Configuration Configuration { get; init; }
|
private DalamudPluginInterface PluginInterface { get; init; }
|
||||||
public WindowSystem WindowSystem = new("SamplePlugin");
|
private CommandManager CommandManager { get; init; }
|
||||||
|
public Configuration Configuration { get; init; }
|
||||||
|
|
||||||
public Plugin(
|
public WindowSystem WindowSystem = new("FFXIV_Vibe_Plugin");
|
||||||
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface,
|
|
||||||
[RequiredVersion("1.0")] CommandManager commandManager)
|
|
||||||
{
|
|
||||||
this.PluginInterface = pluginInterface;
|
|
||||||
this.CommandManager = commandManager;
|
|
||||||
|
|
||||||
this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
// FFXIV_Vibe_Plugin definition
|
||||||
this.Configuration.Initialize(this.PluginInterface);
|
// TODO: private PluginUI PluginUi { get; init; }
|
||||||
|
private FFXIV_Vibe_Plugin.App app;
|
||||||
|
|
||||||
// you might normally want to embed resources and load them from the manifest stream
|
public Plugin(
|
||||||
var imagePath = Path.Combine(PluginInterface.AssemblyLocation.Directory?.FullName!, "goat.png");
|
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface,
|
||||||
var goatImage = this.PluginInterface.UiBuilder.LoadImage(imagePath);
|
[RequiredVersion("1.0")] CommandManager commandManager,
|
||||||
|
[RequiredVersion("1.0")] ClientState clientState,
|
||||||
|
[RequiredVersion("1.0")] GameNetwork gameNetwork,
|
||||||
|
[RequiredVersion("1.0")] SigScanner scanner,
|
||||||
|
[RequiredVersion("1.0")] ObjectTable gameObjects,
|
||||||
|
[RequiredVersion("1.0")] DataManager dataManager
|
||||||
|
) {
|
||||||
|
this.PluginInterface = pluginInterface;
|
||||||
|
this.CommandManager = commandManager;
|
||||||
|
|
||||||
WindowSystem.AddWindow(new ConfigWindow(this));
|
this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||||
WindowSystem.AddWindow(new MainWindow(this, goatImage));
|
this.Configuration.Initialize(this.PluginInterface);
|
||||||
|
|
||||||
this.CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
|
// you might normally want to embed resources and load them from the manifest stream
|
||||||
{
|
var imagePath = Path.Combine(PluginInterface.AssemblyLocation.Directory?.FullName!, "logo.png");
|
||||||
HelpMessage = "A useful message to display in /xlhelp"
|
var logoImage = this.PluginInterface.UiBuilder.LoadImage(imagePath);
|
||||||
});
|
|
||||||
|
|
||||||
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
WindowSystem.AddWindow(new ConfigWindow(this));
|
||||||
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
WindowSystem.AddWindow(new MainWindow(this, logoImage));
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
this.CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand) {
|
||||||
{
|
HelpMessage = "A vibe plugin for fun..."
|
||||||
this.WindowSystem.RemoveAllWindows();
|
});
|
||||||
this.CommandManager.RemoveHandler(CommandName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnCommand(string command, string args)
|
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
||||||
{
|
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
||||||
// in response to the slash command, just display our main ui
|
|
||||||
WindowSystem.GetWindow("My Amazing Window").IsOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawUI()
|
// Init our own app
|
||||||
{
|
this.app = new FFXIV_Vibe_Plugin.App(CommandName, ShortName, gameNetwork, clientState, dataManager, DalamudChat, Configuration, scanner, gameObjects, pluginInterface);
|
||||||
this.WindowSystem.Draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawConfigUI()
|
|
||||||
{
|
|
||||||
WindowSystem.GetWindow("A Wonderful Configuration Window").IsOpen = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
this.WindowSystem.RemoveAllWindows();
|
||||||
|
this.CommandManager.RemoveHandler(CommandName);
|
||||||
|
this.app.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnCommand(string command, string args) {
|
||||||
|
// in response to the slash command, just display our main ui
|
||||||
|
WindowSystem.GetWindow("My Amazing Window").IsOpen = true;
|
||||||
|
this.app.OnCommand(command, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawUI() {
|
||||||
|
this.WindowSystem.Draw();
|
||||||
|
this.app.DrawUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawConfigUI() {
|
||||||
|
WindowSystem.GetWindow("A Wonderful Configuration Window").IsOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,30 @@ using System.Numerics;
|
|||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace FFXIV_Plugin_Vibe.Windows;
|
namespace FFXIV_Vibe_Plugin.Windows;
|
||||||
|
|
||||||
public class ConfigWindow : Window, IDisposable
|
public class ConfigWindow : Window, IDisposable {
|
||||||
{
|
private Configuration Configuration;
|
||||||
private Configuration Configuration;
|
|
||||||
|
|
||||||
public ConfigWindow(Plugin plugin) : base(
|
public ConfigWindow(Plugin plugin) : base(
|
||||||
"A Wonderful Configuration Window",
|
"A Wonderful Configuration Window",
|
||||||
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar |
|
ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar |
|
||||||
ImGuiWindowFlags.NoScrollWithMouse)
|
ImGuiWindowFlags.NoScrollWithMouse) {
|
||||||
{
|
this.Size = new Vector2(232, 75);
|
||||||
this.Size = new Vector2(232, 75);
|
this.SizeCondition = ImGuiCond.Always;
|
||||||
this.SizeCondition = ImGuiCond.Always;
|
|
||||||
|
|
||||||
this.Configuration = plugin.Configuration;
|
this.Configuration = plugin.Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose() { }
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw() {
|
||||||
{
|
// can't ref a property, so use a local copy
|
||||||
// can't ref a property, so use a local copy
|
/*var configValue = this.Configuration.SomePropertyToBeSavedAndWithADefault;
|
||||||
var configValue = this.Configuration.SomePropertyToBeSavedAndWithADefault;
|
if (ImGui.Checkbox("Random Config Bool", ref configValue)) {
|
||||||
if (ImGui.Checkbox("Random Config Bool", ref configValue))
|
this.Configuration.SomePropertyToBeSavedAndWithADefault = configValue;
|
||||||
{
|
// can save immediately on change, if you don't want to provide a "Save and Close" button
|
||||||
this.Configuration.SomePropertyToBeSavedAndWithADefault = configValue;
|
this.Configuration.Save();
|
||||||
// can save immediately on change, if you don't want to provide a "Save and Close" button
|
}*/
|
||||||
this.Configuration.Save();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,45 +4,39 @@ using Dalamud.Interface.Windowing;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
||||||
namespace FFXIV_Plugin_Vibe.Windows;
|
namespace FFXIV_Vibe_Plugin.Windows;
|
||||||
|
|
||||||
public class MainWindow : Window, IDisposable
|
public class MainWindow : Window, IDisposable {
|
||||||
{
|
private TextureWrap GoatImage;
|
||||||
private TextureWrap GoatImage;
|
private Plugin Plugin;
|
||||||
private Plugin Plugin;
|
|
||||||
|
|
||||||
public MainWindow(Plugin plugin, TextureWrap goatImage) : base(
|
public MainWindow(Plugin plugin, TextureWrap goatImage) : base(
|
||||||
"My Amazing Window", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
|
"My Amazing Window", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) {
|
||||||
{
|
this.SizeConstraints = new WindowSizeConstraints {
|
||||||
this.SizeConstraints = new WindowSizeConstraints
|
MinimumSize = new Vector2(375, 330),
|
||||||
{
|
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
||||||
MinimumSize = new Vector2(375, 330),
|
};
|
||||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.GoatImage = goatImage;
|
this.GoatImage = goatImage;
|
||||||
this.Plugin = plugin;
|
this.Plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
this.GoatImage.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw() {
|
||||||
|
/*ImGui.Text($"The random config bool is {this.Plugin.Configuration.SomePropertyToBeSavedAndWithADefault}");*/
|
||||||
|
|
||||||
|
if (ImGui.Button("Show Settings")) {
|
||||||
|
this.Plugin.DrawConfigUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
ImGui.Spacing();
|
||||||
{
|
|
||||||
this.GoatImage.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Draw()
|
ImGui.Text("Have a goat:");
|
||||||
{
|
ImGui.Indent(55);
|
||||||
ImGui.Text($"The random config bool is {this.Plugin.Configuration.SomePropertyToBeSavedAndWithADefault}");
|
ImGui.Image(this.GoatImage.ImGuiHandle, new Vector2(this.GoatImage.Width, this.GoatImage.Height));
|
||||||
|
ImGui.Unindent(55);
|
||||||
if (ImGui.Button("Show Settings"))
|
}
|
||||||
{
|
|
||||||
this.Plugin.DrawConfigUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Spacing();
|
|
||||||
|
|
||||||
ImGui.Text("Have a goat:");
|
|
||||||
ImGui.Indent(55);
|
|
||||||
ImGui.Image(this.GoatImage.ImGuiHandle, new Vector2(this.GoatImage.Width, this.GoatImage.Height));
|
|
||||||
ImGui.Unindent(55);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,37 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net7.0-windows7.0": {
|
"net7.0-windows7.0": {
|
||||||
|
"Buttplug": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.0.6, )",
|
||||||
|
"resolved": "2.0.6",
|
||||||
|
"contentHash": "UNusT8YfG+KMYN7ZG0IPjqGbgOTGDm83n0N6zpF11QxciwyBiAcs7NHJquImuJdrDI7ygVtzb2VepGVrIH5HDw==",
|
||||||
|
"dependencies": {
|
||||||
|
"ButtplugRustFFI": "2.0.5",
|
||||||
|
"Google.Protobuf": "3.19.1",
|
||||||
|
"System.Memory": "4.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"DalamudPackager": {
|
"DalamudPackager": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.1.10, )",
|
"requested": "[2.1.10, )",
|
||||||
"resolved": "2.1.10",
|
"resolved": "2.1.10",
|
||||||
"contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw=="
|
"contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw=="
|
||||||
|
},
|
||||||
|
"ButtplugRustFFI": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.0.5",
|
||||||
|
"contentHash": "1uwoNtiYysP5LvYJJYGYcOBSh6wLNQhpt8M1Mdeu4TEcM7LvMRQbjiIN6Bhd6W5Xpk1H+75kP3j/GFIsQyecUw=="
|
||||||
|
},
|
||||||
|
"Google.Protobuf": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "3.19.1",
|
||||||
|
"contentHash": "M6yun2BPdHkBjD3V14muZSt72azWHRJEx88ME2TyyH2+/ww6R3hIptjBFQQtO6pmkfLXW/NGQ4hADWSa9AmK2A=="
|
||||||
|
},
|
||||||
|
"System.Memory": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.5.4",
|
||||||
|
"contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user