diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..dea2eff
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,304 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:suggestion
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+
+# Parentheses preferences
+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_other_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:silent
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = false:suggestion
+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:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
+dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = true
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
+csharp_style_expression_bodied_constructors = false:suggestion
+csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
+csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion
+csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
+csharp_style_expression_bodied_methods = when_on_single_line:suggestion
+csharp_style_expression_bodied_operators = when_on_single_line:suggestion
+csharp_style_expression_bodied_properties = when_on_single_line:suggestion
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:warning
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true:warning
+
+# Code-block preferences
+csharp_prefer_braces = when_multiline:warning
+csharp_prefer_simple_using_statement = true
+csharp_style_namespace_declarations = block_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:suggestion
+csharp_style_prefer_top_level_statements = true
+
+# Expression-level preferences
+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:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_local_over_anonymous_function = true:warning
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true:warning
+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
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:suggestion
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false:warning
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = false:suggestion
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = flush_left
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_ipascalcase.severity = warning
+dotnet_naming_rule.interface_should_be_ipascalcase.symbols = interface
+dotnet_naming_rule.interface_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.types_should_be_pascalcase.severity = warning
+dotnet_naming_rule.types_should_be_pascalcase.symbols = types
+dotnet_naming_rule.types_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.async_methods_should_be_pascalcaseasync.severity = suggestion
+dotnet_naming_rule.async_methods_should_be_pascalcaseasync.symbols = async_methods
+dotnet_naming_rule.async_methods_should_be_pascalcaseasync.style = pascalcaseasync
+
+dotnet_naming_rule.method_like_members_should_be_pascalcase.severity = warning
+dotnet_naming_rule.method_like_members_should_be_pascalcase.symbols = method_like_members
+dotnet_naming_rule.method_like_members_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_members_should_be_pascalcase.severity = warning
+dotnet_naming_rule.public_members_should_be_pascalcase.symbols = public_members
+dotnet_naming_rule.public_members_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.constants_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.constants_should_be_pascalcase.symbols = constants
+dotnet_naming_rule.constants_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_field_should_be__camelcase.severity = warning
+dotnet_naming_rule.private_field_should_be__camelcase.symbols = private_field
+dotnet_naming_rule.private_field_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.class_internal_fields_should_be_camelcase.severity = warning
+dotnet_naming_rule.class_internal_fields_should_be_camelcase.symbols = class_internal_fields
+dotnet_naming_rule.class_internal_fields_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.locals_should_be_camelcase.severity = silent
+dotnet_naming_rule.locals_should_be_camelcase.symbols = locals
+dotnet_naming_rule.locals_should_be_camelcase.style = camelcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.private_field.applicable_kinds = field
+dotnet_naming_symbols.private_field.applicable_accessibilities = private
+dotnet_naming_symbols.private_field.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, enum, delegate
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.class_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.class_internal_fields.applicable_accessibilities = protected, private_protected
+dotnet_naming_symbols.class_internal_fields.required_modifiers =
+
+dotnet_naming_symbols.method_like_members.applicable_kinds = property, field, event, delegate, method, local_function
+dotnet_naming_symbols.method_like_members.applicable_accessibilities = *
+dotnet_naming_symbols.method_like_members.required_modifiers =
+
+dotnet_naming_symbols.locals.applicable_kinds = parameter, local
+dotnet_naming_symbols.locals.applicable_accessibilities = local
+dotnet_naming_symbols.locals.required_modifiers =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.public_members.applicable_kinds = property, field, event, method
+dotnet_naming_symbols.public_members.applicable_accessibilities = public, internal, protected_internal
+dotnet_naming_symbols.public_members.required_modifiers =
+
+dotnet_naming_symbols.constants.applicable_kinds = field, local
+dotnet_naming_symbols.constants.applicable_accessibilities = *
+dotnet_naming_symbols.constants.required_modifiers = const
+
+dotnet_naming_symbols.async_methods.applicable_kinds = method, local_function
+dotnet_naming_symbols.async_methods.applicable_accessibilities = *
+dotnet_naming_symbols.async_methods.required_modifiers = async
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.pascalcaseasync.required_prefix =
+dotnet_naming_style.pascalcaseasync.required_suffix = Async
+dotnet_naming_style.pascalcaseasync.word_separator =
+dotnet_naming_style.pascalcaseasync.capitalization = pascal_case
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..1e0e9bb
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,129 @@
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
+
+name: Build
+
+# Trigger the action on push to master
+on:
+ workflow_call: {} # Allow reusing this workflow
+ push:
+ branches:
+ - master # Run for pushes to master
+ pull_request:
+ branches:
+ - '*' # Run the workflow for all pull requests
+
+# Sets permissions of the GITHUB_TOKEN to allow reading packages
+permissions:
+ packages: read
+
+env:
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ DOTNET_NOLOGO: true
+ NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NuGetDirectory: ${{ github.workspace}}/nuget
+
+defaults:
+ run:
+ shell: pwsh
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "ci"
+ cancel-in-progress: false
+
+jobs:
+ Build:
+ runs-on: ubuntu-latest
+ steps:
+ # Setup environment
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+ source-url: https://nuget.pkg.github.com/ResoniteModdingGroup/index.json
+
+ - name: Add MonkeyLoader NuGet Source
+ run: dotnet nuget add source https://pkg.munally.com/MonkeyModdingTroop/index.json
+
+ - name: Restore NuGet Package Cache
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget
+
+ # Build and test projects
+ - name: Restore
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Test
+ run: dotnet test --no-restore --no-build
+
+ - name: Move NuGet Packages
+ run: mv (Get-ChildItem -Recurse ./ -Include *.nupkg) ./
+
+ # Removes the version number from the package name
+ - name: Rename NuGet Packages
+ run: Get-ChildItem -Include *.nupkg -Path ./* | Rename-Item -NewName { $_.Name -Replace '\.\d+\.\d+\.\d+.*$','.nupkg' }
+
+ # Publish the NuGet package(s) as an artifact, so they can be used in the following jobs
+ - name: Upload NuGet Packages Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: NuGet Packages
+ if-no-files-found: error
+ retention-days: 7
+ path: ./*.nupkg
+
+ # Only when it's not from a PR to avoid any funny packages in the cache
+ - name: Save NuGet Package Cache
+ if: ${{ github.event_name != 'pull_request' }}
+ uses: actions/cache/save@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+
+ Validate-NuGet:
+ runs-on: ubuntu-latest
+ needs: [ Build ]
+ steps:
+ - name: Setup Dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+ source-url: https://nuget.pkg.github.com/ResoniteModdingGroup/index.json
+
+ - name: Add MonkeyLoader NuGet Source
+ run: dotnet nuget add source https://pkg.munally.com/MonkeyModdingTroop/index.json
+
+ - name: Restore NuGet Package Cache
+ uses: actions/cache/restore@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget
+
+ - name: Install NuGet Validator
+ run: dotnet tool update Meziantou.Framework.NuGetPackageValidation.Tool --global
+
+ - name: Download NuGet Packages Artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: NuGet Packages
+ path: ${{ env.NuGetDirectory }}
+
+ # Validate metadata and content of the NuGet package
+ # https://www.nuget.org/packages/Meziantou.Framework.NuGetPackageValidation.Tool#readme-body-tab
+ # If some rules are not applicable, you can disable them
+ # using the --excluded-rules or --excluded-rule-ids option
+ - name: Validate Package(s)
+ run: meziantou.validate-nuget-package (Get-ChildItem -Recurse "${{ env.NuGetDirectory }}" -Include *.nupkg) --excluded-rules IconMustBeSet
\ No newline at end of file
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..059338c
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,84 @@
+# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
+
+name: Publish
+
+on:
+ push:
+ tags:
+ - v** # Only when a v... tag is pushed
+
+# Sets permissions of the GITHUB_TOKEN to allow writing packages and push releases
+permissions:
+ packages: write
+ contents: write
+
+env:
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
+ DOTNET_NOLOGO: true
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NUGET_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ NuGetDirectory: ${{ github.workspace}}/nuget
+
+defaults:
+ run:
+ shell: pwsh
+
+jobs:
+ Build:
+ uses: ./.github/workflows/build.yml
+
+ Release:
+ #if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ needs: [ build ]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Download NuGet Packages Artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: NuGet Packages
+ path: ${{ env.NuGetDirectory }}
+
+ - name: Build Changelog
+ id: build_changelog
+ uses: mikepenz/release-changelog-builder-action@v4
+
+ - name: Create Release
+ uses: mikepenz/action-gh-release@v1 #softprops/action-gh-release
+ with:
+ body: ${{steps.build_changelog.outputs.changelog}}
+ files: ${{ env.NuGetDirectory }}/*.nupkg
+ fail_on_unmatched_files: true
+ fail_on_asset_upload_issue: true
+ prerelease: ${{ contains(github.ref, '-') }} # simple check for vX.Y.Z-something
+
+ Publish-GitHub:
+ #if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ needs: [ build ]
+ steps:
+ - name: Download NuGet Packages Artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: NuGet Packages
+ path: ${{ env.NuGetDirectory }}
+
+ - name: Setup Dotnet
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+ source-url: https://nuget.pkg.github.com/ResoniteModdingGroup/index.json
+
+ - name: Add MonkeyLoader NuGet Source
+ run: dotnet nuget add source https://pkg.munally.com/MonkeyModdingTroop/index.json
+
+ # Publish all NuGet packages to the GitHub feed
+ # Use --skip-duplicate to prevent errors if a package with the same version already exists.
+ # If you retry a failed workflow, already published packages will be skipped without error.
+ - name: Publish NuGet Packages
+ run: |
+ foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) {
+ dotnet nuget push $file --api-key "${{ secrets.GITHUB_TOKEN }}" --source https://nuget.pkg.github.com/ResoniteModdingGroup/index.json --skip-duplicate
+ }
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3936b13
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,365 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+/Resonite
diff --git a/CodeMaid.config b/CodeMaid.config
new file mode 100644
index 0000000..92a105c
--- /dev/null
+++ b/CodeMaid.config
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+
+
+
+
+ 8
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ 11
+
+
+ False
+
+
+ Segoe UI
+
+
+ 2
+
+
+ 1
+
+
+ True
+
+
+ 10
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ using System;||using System.Collections.Generic;||using System.Linq;
+
+
+ True
+
+
+ True
+
+
+ 3
+
+
+ 12
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 0
+
+
+ True
+
+
+ 100
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ False
+
+
+ True
+
+
+ True
+
+
+ 6
+
+
+ True
+
+
+ .*\.Designer\.cs||.*\.Designer\.vb||.*\.resx
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 2
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 7
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 0
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ 4
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ False
+
+
+ 15
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 5
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 9
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ False
+
+
+ True
+
+
+ 10
+
+
+ True
+
+
+ 0
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ .cpp .h||.xaml .xaml.cs||.xml .xsd||.ascx .ascx.cs||.aspx .aspx.cs||.master .master.cs
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ 17
+
+
+ True
+
+
+ True
+
+
+ False
+
+
+ Classes||6||Structs + Classes + Enums + Interfaces
+
+
+ False
+
+
+ False
+
+
+ Indexers||2||Properties + Indexers
+
+
+ False
+
+
+ 100
+
+
+ True
+
+
+ False
+
+
+ False
+
+
+ Enums||6||Structs + Classes + Enums + Interfaces
+
+
+ Properties||2||Properties + Indexers
+
+
+ False
+
+
+ Structs||6||Structs + Classes + Enums + Interfaces
+
+
+ Methods||4||Methods
+
+
+ Destructors||3||Constructors + Destructors
+
+
+ True
+
+
+ Fields||1||Fields
+
+
+ 1
+
+
+ Constructors||3||Constructors + Destructors
+
+
+ Events||5||Delegates + Events
+
+
+ Delegates||5||Delegates + Events
+
+
+ Interfaces||6||Structs + Classes + Enums + Interfaces
+
+
+ False
+
+
+ 0
+
+
+ 2
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..18be00b
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,43 @@
+
+
+ Library
+ net462
+ $(AssemblyTitle).dll
+ 11.0
+ enable
+ true
+ True
+ portable
+ True
+
+ true
+
+
+ https://pkg.munally.com/MonkeyModdingTroop/index.json;
+ https://nuget.pkg.github.com/ResoniteModdingGroup/index.json
+
+
+
+
+ README.md
+ $(PackageProjectUrl).git
+ git
+ False
+ True
+ True
+ snupkg
+ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
+
+
+
+ true
+ false
+
+
+
+ $(MSBuildThisFileDirectory)Resonite
+ C:\Program Files (x86)\Steam\steamapps\common\Resonite
+ $(HOME)/.steam/steam/steamapps/common/Resonite
+ D:/Files/Games/Resonite/app
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..20179b0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+Community Bug-Fix Collection
+============================
+
+A [MonkeyLoader](https://github.com/MonkeyModdingTroop/MonkeyLoader) mod for
+[Resonite](https://resonite.com/) that fixes various small
+[Resonite Issues](https://github.com/Yellow-Dog-Man/Resonite-Issues/issues)
+that are still open.
+
+
+## Install
+
+First, make sure you've installed MonkeyLoader and the necessary GamePacks - combined releases can be found on the page of the Resonite GamePack here: https://github.com/ResoniteModdingGroup/MonkeyLoader.GamePacks.Resonite/releases/
+
+Then all you have to do is placing the provided `CommunityBugFixCollection.nupkg` into your `Resonite/MonkeyLoader/Mods/` folder.
+
+
+## Fixes
+
+The issues fixed by this mod will be linked in the following list.
+If any of them have been closed and not removed from the mod,
+just disable them in the settings in the meantime.
+
+* World crash when a (multi)tool is scaled to zero (https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/98)
\ No newline at end of file
diff --git a/WikiIntegration.sln b/WikiIntegration.sln
new file mode 100644
index 0000000..889a486
--- /dev/null
+++ b/WikiIntegration.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33516.290
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WikiIntegration", "WikiIntegration\WikiIntegration.csproj", "{40835522-1978-431B-A395-0870583B80F5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07124CF9-EE3D-4A7E-A2F8-8FD07966E423}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {40835522-1978-431B-A395-0870583B80F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {40835522-1978-431B-A395-0870583B80F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {40835522-1978-431B-A395-0870583B80F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {40835522-1978-431B-A395-0870583B80F5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7D505B2E-FBA2-4F50-BC1C-838BC8DF0D5C}
+ EndGlobalSection
+EndGlobal
diff --git a/WikiIntegration/Locale/de.json b/WikiIntegration/Locale/de.json
new file mode 100644
index 0000000..f7fb65c
--- /dev/null
+++ b/WikiIntegration/Locale/de.json
@@ -0,0 +1,14 @@
+{
+ "localeCode": "de",
+ "authors": [ "Banane9" ],
+ "messages": {
+ "WikiIntegration.Name": "Wiki Integration",
+ "WikiIntegration.Description": "Diese Mod fügt Links zur Wiki zu Worker Inspektoren und ProtoFlux Nodes hinzu.",
+
+ "WikiIntegration.WikiHyperlink.Component": "Öffnet den Resonite Wiki Artikel über diese Komponente.",
+ "WikiIntegration.WikiHyperlink.ProtoFlux": "Öffnet den Resonite Wiki Artikel über diese ProtoFlux Node.",
+
+ "WikiIntegration.OpenWikiArticleButton.Name": "Öffne Wiki Artikel Link",
+ "WikiIntegration.OpenWikiArticleButton.Description": "Fügt den Resonite Wiki Artikel Link zu Komponenten in Worker Inspektoren und ProtoFlux Nodes hinzu."
+ }
+}
\ No newline at end of file
diff --git a/WikiIntegration/Locale/en.json b/WikiIntegration/Locale/en.json
new file mode 100644
index 0000000..cb620d2
--- /dev/null
+++ b/WikiIntegration/Locale/en.json
@@ -0,0 +1,14 @@
+{
+ "localeCode": "en",
+ "authors": [ "Banane9" ],
+ "messages": {
+ "WikiIntegration.Name": "Wiki Integration",
+ "WikiIntegration.Description": "This mod adds links to the Wiki to Components in Worker Inspectors, and ProtoFlux nodes.",
+
+ "WikiIntegration.WikiHyperlink.Component": "Opens the Resonite Wiki article about this Component.",
+ "WikiIntegration.WikiHyperlink.ProtoFlux": "Opens the Resonite Wiki article about this ProtoFlux node.",
+
+ "WikiIntegration.OpenWikiArticleButton.Name": "Open Wiki Article Button",
+ "WikiIntegration.OpenWikiArticleButton.Description": "Adds the Resonite Wiki button to Components in Worker Inspectors, and ProtoFlux nodes."
+ }
+}
\ No newline at end of file
diff --git a/WikiIntegration/OpenWikiArticleButton.cs b/WikiIntegration/OpenWikiArticleButton.cs
new file mode 100644
index 0000000..1aaf9ca
--- /dev/null
+++ b/WikiIntegration/OpenWikiArticleButton.cs
@@ -0,0 +1,96 @@
+using Elements.Core;
+using FrooxEngine;
+using FrooxEngine.ProtoFlux;
+using FrooxEngine.UIX;
+using HarmonyLib;
+using MonkeyLoader.Resonite;
+using MonkeyLoader.Resonite.UI;
+using ProtoFlux.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WikiIntegration
+{
+ [HarmonyPatchCategory(nameof(OpenWikiArticleButton))]
+ [HarmonyPatch(typeof(ProtoFluxNodeVisual), nameof(ProtoFluxNodeVisual.GenerateVisual))]
+ internal sealed class OpenWikiArticleButton : ConfiguredResoniteInspectorMonkey
+ {
+ private static readonly Lazy _componentLocale = new(() => Mod.GetLocaleString("WikiHyperlink.Component"));
+ private static readonly Lazy _protoFluxLocale = new(() => Mod.GetLocaleString("WikiHyperlink.ProtoFlux"));
+
+ public override int Priority => HarmonyLib.Priority.HigherThanNormal;
+
+ private static LocaleString ComponentLocale => _componentLocale.Value;
+ private static LocaleString ProtoFluxLocale => _protoFluxLocale.Value;
+
+ protected override void Handle(BuildInspectorHeaderEvent eventData)
+ {
+ var ui = eventData.UI;
+
+ ui.PushStyle();
+ ui.Style.FlexibleWidth = 0;
+ ui.Style.MinWidth = 40;
+
+ var button = ui.Button(OfficialAssets.Graphics.Badges.Mentor)
+ .WithTooltip(eventData.Worker is ProtoFluxNode ? ProtoFluxLocale : ComponentLocale);
+
+ AddHyperlink(button.Slot, eventData.Worker);
+ ConfigSection.ComponentOffset.DriveFromVariable(button.Slot._orderOffset);
+
+ ui.PopStyle();
+ }
+
+ private static void AddHyperlink(Slot slot, Worker worker)
+ {
+ string wikiPage;
+ LocaleString reason;
+
+ if (worker is ProtoFluxNode node)
+ {
+ reason = ProtoFluxLocale;
+ var nodeName = node.NodeName;
+
+ var nodeMetadata = NodeMetadataHelper.GetMetadata(node.NodeType);
+ if (!string.IsNullOrEmpty(nodeMetadata.Overload))
+ {
+ var overload = nodeMetadata.Overload;
+ var dotIndex = overload.LastIndexOf('.');
+
+ nodeName = dotIndex > 0 ? overload[(dotIndex + 1)..] : nodeName;
+ }
+
+ wikiPage = $"ProtoFlux:{nodeName.Replace(' ', '_')}";
+ }
+ else
+ {
+ reason = ComponentLocale;
+ var workerName = worker.WorkerType.Name;
+
+ // Don't need to remove the `1 on generics - they redirect
+ wikiPage = $"Component:{workerName}";
+ }
+
+ var hyperlink = slot.AttachComponent();
+ hyperlink.URL.Value = new Uri($"https://wiki.resonite.com/{wikiPage}");
+ hyperlink.Reason.AssignLocaleString(reason);
+ }
+
+ private static void Postfix(ProtoFluxNodeVisual __instance, ProtoFluxNode node)
+ {
+ var ui = new UIBuilder(__instance.LocalUIBuilder.Canvas);
+
+ var buttonArea = ui.Panel();
+ ui.IgnoreLayout();
+ buttonArea.AnchorMin.Value = new(1, 0);
+ buttonArea.AnchorMax.Value = new(1, 0);
+ buttonArea.OffsetMin.Value = new(-12, 2);
+ buttonArea.OffsetMax.Value = new(-2, 12);
+
+ var button = ui.Image(OfficialAssets.Graphics.Badges.Mentor);
+ button.Slot.AttachComponent