Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edab5c7a6d | |||
| 82cbf4c281 | |||
| 00ae81751b | |||
| 89384702b4 | |||
| 54316313dc | |||
| 4059b363a3 | |||
| 0220e5d756 | |||
| 2315f10d91 | |||
| 3283e51381 | |||
| 7e960371a3 | |||
| f2a2daf39d | |||
| 7d87f1c4fe | |||
| fe84fd558e | |||
| 624ad20404 | |||
| 54ff88d6d4 | |||
| c955f30422 | |||
| 7a1bd1babc | |||
| d0be75e79d | |||
| e0ead86616 | |||
| b66005daea | |||
| 0fe66d2c3c | |||
| 169168cea9 | |||
| f6d3794d87 | |||
| 763f5a3f5d | |||
| 8a18f7caaa | |||
| 5f7bfb5890 | |||
| 3be4e73c27 | |||
| 667950c98e | |||
| 3e91177833 | |||
| 51f18e46a0 | |||
| f66316161b | |||
| 679b8f0f5e | |||
| 0e470fcdce | |||
| abbbf95002 | |||
| fbbbeebade | |||
| 7c9b90c767 | |||
| b81894b859 | |||
| 655c903cb5 | |||
| 8c4afaac17 | |||
| c6a3780753 | |||
| d9f6704316 | |||
| 011490368b | |||
| 8ed10a536b | |||
| 6051e49307 |
+237
-152
@@ -1,167 +1,252 @@
|
||||
[*]
|
||||
indent_style=space
|
||||
tab_width=4
|
||||
indent_size=4
|
||||
trim_trailing_whitespace=true
|
||||
insert_final_newline=false
|
||||
# ##############################################################
|
||||
# #
|
||||
# # .editorconfig – Hellion Forge / Hellion Media
|
||||
# #
|
||||
# # Überarbeitet: Mai 2026
|
||||
# #
|
||||
# # Strategie:
|
||||
# # - Standard-.NET-Conventions (private Fields = _camelCase)
|
||||
# # - CSharpier übernimmt die meiste Formatierung
|
||||
# # - Hier: Naming, IDE-Hints, Backup-Format-Regeln
|
||||
# #
|
||||
# # ##############################################################
|
||||
|
||||
# JetBrains Rider custom properties for code formatting styles
|
||||
resharper_csharp_brace_style=next_line
|
||||
# Allman für standard Tooling (VS Code)
|
||||
root = true
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Defaults (alle Files)
|
||||
# =====================================================
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Markdown: Trailing Spaces erlaubt (2 Spaces = <br>)
|
||||
# =====================================================
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
|
||||
# =====================================================
|
||||
# JSON / YAML / Web-Configs: 2-Space-Indent
|
||||
# Konsistent mit yamllint und Prettier-Override
|
||||
# =====================================================
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{json,jsonc,har,jsb2,jsb3,postman_collection,postman_environment}]
|
||||
indent_size = 2
|
||||
|
||||
[{.babelrc,.eslintrc,.prettierrc,.markdownlintrc,.stylelintrc,bowerrc}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
# =====================================================
|
||||
# .NET / XAML / Razor / Resources: 4-Space-Indent
|
||||
# =====================================================
|
||||
|
||||
[*.{cs,csx,vb,fs,fsi,fsx}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{xml,xsd,xaml,axaml,paml,resx,resw,nuspec,config}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{cshtml,razor,aspx,ascx,asax,master,axaml}]
|
||||
indent_size = 4
|
||||
|
||||
|
||||
# ##############################################################
|
||||
# #
|
||||
# # C# Sektion: Style, Naming, Format
|
||||
# #
|
||||
# ##############################################################
|
||||
|
||||
[*.{cs,csx}]
|
||||
|
||||
|
||||
# =====================================================
|
||||
# C# Style – var-Präferenz
|
||||
# =====================================================
|
||||
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
|
||||
# =====================================================
|
||||
# C# Style – Sonstiges
|
||||
# =====================================================
|
||||
|
||||
# UTF-8 String Literals (C# 11+)
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
|
||||
# Reihenfolge der Access-Modifier (Microsoft-Empfehlung)
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
|
||||
|
||||
# Initializer: nicht alles auf eine Zeile
|
||||
csharp_new_line_before_members_in_object_initializers = false
|
||||
|
||||
|
||||
# =====================================================
|
||||
# C# Format – Braces (Backup, falls CSharpier nicht läuft)
|
||||
# =====================================================
|
||||
|
||||
# Allman Style: Klammern auf neue Zeile
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
|
||||
# Switch-Einrückung
|
||||
|
||||
# =====================================================
|
||||
# C# Format – Switch-Einrückung
|
||||
# =====================================================
|
||||
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_switch_labels = true
|
||||
|
||||
resharper_csharp_braces_for_foreach=not_required
|
||||
resharper_csharp_braces_for_for=not_required
|
||||
resharper_csharp_braces_for_while=not_required
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
|
||||
# Microsoft .NET properties
|
||||
csharp_new_line_before_members_in_object_initializers=false
|
||||
csharp_preferred_modifier_order=public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion
|
||||
csharp_style_prefer_utf8_string_literals=true:suggestion
|
||||
csharp_style_var_elsewhere=true:suggestion
|
||||
csharp_style_var_for_built_in_types=true:suggestion
|
||||
csharp_style_var_when_type_is_apparent=true:suggestion
|
||||
dotnet_naming_rule.private_constants_rule.import_to_resharper=True
|
||||
dotnet_naming_rule.private_constants_rule.resharper_description=Constant fields (private)
|
||||
dotnet_naming_rule.private_constants_rule.resharper_guid=236f7aa5-7b06-43ca-bf2a-9b31bfcff09a
|
||||
dotnet_naming_rule.private_constants_rule.severity=warning
|
||||
dotnet_naming_rule.private_constants_rule.style=upper_camel_case_style
|
||||
dotnet_naming_rule.private_constants_rule.symbols=private_constants_symbols
|
||||
dotnet_naming_rule.private_instance_fields_rule.import_to_resharper=True
|
||||
dotnet_naming_rule.private_instance_fields_rule.resharper_description=Instance fields (private)
|
||||
dotnet_naming_rule.private_instance_fields_rule.resharper_guid=4a98fdf6-7d98-4f5a-afeb-ea44ad98c70c
|
||||
dotnet_naming_rule.private_instance_fields_rule.severity=warning
|
||||
dotnet_naming_rule.private_instance_fields_rule.style=upper_camel_case_style
|
||||
dotnet_naming_rule.private_instance_fields_rule.symbols=private_instance_fields_symbols
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.import_to_resharper=True
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.resharper_description=Instance fields (private)
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.resharper_guid=4a98fdf6-7d98-4f5a-afeb-ea44ad98c70c
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.severity=warning
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.style=upper_camel_case_style
|
||||
dotnet_naming_rule.private_instance_fields_rule_1.symbols=private_instance_fields_symbols_1
|
||||
dotnet_naming_rule.private_static_fields_rule.import_to_resharper=True
|
||||
dotnet_naming_rule.private_static_fields_rule.resharper_description=Static fields (private)
|
||||
dotnet_naming_rule.private_static_fields_rule.resharper_guid=f9fce829-e6f4-4cb2-80f1-5497c44f51df
|
||||
dotnet_naming_rule.private_static_fields_rule.severity=warning
|
||||
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.import_to_resharper=True
|
||||
dotnet_naming_rule.private_static_readonly_rule.resharper_description=Static readonly fields (private)
|
||||
dotnet_naming_rule.private_static_readonly_rule.resharper_guid=15b5b1f1-457c-4ca6-b278-5615aedc07d3
|
||||
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_rule.unity_serialized_field_rule.import_to_resharper=True
|
||||
dotnet_naming_rule.unity_serialized_field_rule.resharper_description=Unity serialized field
|
||||
dotnet_naming_rule.unity_serialized_field_rule.resharper_guid=5f0fdb63-c892-4d2c-9324-15c80b22a7ef
|
||||
dotnet_naming_rule.unity_serialized_field_rule.severity=warning
|
||||
dotnet_naming_rule.unity_serialized_field_rule.style=lower_camel_case_style_1
|
||||
dotnet_naming_rule.unity_serialized_field_rule.symbols=unity_serialized_field_symbols
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.import_to_resharper=True
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.resharper_description=Unity serialized field
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.resharper_guid=5f0fdb63-c892-4d2c-9324-15c80b22a7ef
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.severity=warning
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.style=lower_camel_case_style_1
|
||||
dotnet_naming_rule.unity_serialized_field_rule_1.symbols=unity_serialized_field_symbols_1
|
||||
dotnet_naming_style.lower_camel_case_style.capitalization=camel_case
|
||||
dotnet_naming_style.lower_camel_case_style.required_prefix=_
|
||||
dotnet_naming_style.lower_camel_case_style_1.capitalization=camel_case
|
||||
dotnet_naming_style.upper_camel_case_style.capitalization=pascal_case
|
||||
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_constants_symbols.resharper_applicable_kinds=constant_field
|
||||
dotnet_naming_symbols.private_constants_symbols.resharper_required_modifiers=any
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities=private
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds=field
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.resharper_applicable_kinds=field,readonly_field
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.resharper_required_modifiers=instance
|
||||
dotnet_naming_symbols.private_instance_fields_symbols_1.applicable_accessibilities=private
|
||||
dotnet_naming_symbols.private_instance_fields_symbols_1.applicable_kinds=field
|
||||
dotnet_naming_symbols.private_instance_fields_symbols_1.resharper_applicable_kinds=field,readonly_field
|
||||
dotnet_naming_symbols.private_instance_fields_symbols_1.resharper_required_modifiers=instance
|
||||
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_fields_symbols.resharper_applicable_kinds=field
|
||||
dotnet_naming_symbols.private_static_fields_symbols.resharper_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=readonly,static
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.resharper_applicable_kinds=readonly_field
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.resharper_required_modifiers=static
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities=*
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds=
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds=unity_serialised_field
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers=instance
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_accessibilities=*
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols_1.applicable_kinds=
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_applicable_kinds=unity_serialised_field
|
||||
dotnet_naming_symbols.unity_serialized_field_symbols_1.resharper_required_modifiers=instance
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_other_binary_operators=always_for_clarity:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
|
||||
dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access=true:suggestion
|
||||
dotnet_style_qualification_for_event=false:suggestion
|
||||
dotnet_style_qualification_for_field=false:suggestion
|
||||
dotnet_style_qualification_for_method=false:suggestion
|
||||
dotnet_style_qualification_for_property=false:suggestion
|
||||
dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
|
||||
# =====================================================
|
||||
# .NET Style – Qualification (kein "this." nötig)
|
||||
# =====================================================
|
||||
|
||||
# ReSharper properties
|
||||
resharper_autodetect_indent_settings=true
|
||||
resharper_cpp_insert_final_newline=true
|
||||
resharper_csharp_insert_final_newline=false
|
||||
resharper_formatter_off_tag=@formatter:off
|
||||
resharper_formatter_on_tag=@formatter:on
|
||||
resharper_formatter_tags_enabled=true
|
||||
resharper_fsharp_insert_final_newline=false
|
||||
resharper_html_insert_final_newline=false
|
||||
resharper_resx_insert_final_newline=false
|
||||
resharper_shaderlab_insert_final_newline=false
|
||||
resharper_t4_insert_final_newline=false
|
||||
resharper_use_indent_from_vs=false
|
||||
resharper_vb_insert_final_newline=false
|
||||
resharper_xmldoc_insert_final_newline=false
|
||||
resharper_xml_insert_final_newline=false
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
|
||||
# ReSharper inspection severities
|
||||
resharper_arrange_redundant_parentheses_highlighting=hint
|
||||
resharper_arrange_this_qualifier_highlighting=hint
|
||||
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=hint
|
||||
resharper_razor_assembly_not_resolved_highlighting=warning
|
||||
resharper_redundant_base_qualifier_highlighting=warning
|
||||
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_web_config_module_not_resolved_highlighting=warning
|
||||
resharper_web_config_type_not_resolved_highlighting=warning
|
||||
resharper_web_config_wrong_module_highlighting=warning
|
||||
|
||||
[{*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.prettierrc.json,.markdownlint.json,.yamllint.json,.stylelintrc,bowerrc,jest.config}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
# =====================================================
|
||||
# .NET Style – Predefined Types (int statt Int32 etc.)
|
||||
# =====================================================
|
||||
|
||||
[{*.yaml,*.yml}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,c++m,cc,ccm,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,cxxm,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,mxx,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,uxml,vb,xaml,xamlx,xoml,xsd}]
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
tab_width=4
|
||||
[*.md]
|
||||
trim_trailing_whitespace=false
|
||||
|
||||
# =====================================================
|
||||
# .NET Style – Parentheses
|
||||
# =====================================================
|
||||
|
||||
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
|
||||
|
||||
|
||||
# =====================================================
|
||||
# .NET Style – Accessibility-Modifier erzwingen
|
||||
# =====================================================
|
||||
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
|
||||
# ##############################################################
|
||||
# #
|
||||
# # Naming Conventions (.NET-Standard)
|
||||
# #
|
||||
# # Private Instance Fields: _camelCase
|
||||
# # Private Static Fields: _camelCase
|
||||
# # Private Constants: PascalCase
|
||||
# # Private Static Readonly: PascalCase
|
||||
# #
|
||||
# ##############################################################
|
||||
|
||||
# === Style: Underscore + camelCase ===
|
||||
dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case
|
||||
dotnet_naming_style.underscore_camel_case_style.required_prefix = _
|
||||
|
||||
# === Style: PascalCase ===
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
|
||||
# === Rule: Private Instance Fields → _camelCase ===
|
||||
dotnet_naming_rule.private_instance_fields.severity = warning
|
||||
dotnet_naming_rule.private_instance_fields.symbols = private_instance_fields_symbols
|
||||
dotnet_naming_rule.private_instance_fields.style = underscore_camel_case_style
|
||||
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
|
||||
|
||||
|
||||
# === Rule: Private Static Fields → _camelCase ===
|
||||
dotnet_naming_rule.private_static_fields.severity = warning
|
||||
dotnet_naming_rule.private_static_fields.symbols = private_static_fields_symbols
|
||||
dotnet_naming_rule.private_static_fields.style = underscore_camel_case_style
|
||||
|
||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||
|
||||
|
||||
# === Rule: Private Constants → PascalCase ===
|
||||
dotnet_naming_rule.private_constants.severity = warning
|
||||
dotnet_naming_rule.private_constants.symbols = private_constants_symbols
|
||||
dotnet_naming_rule.private_constants.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
|
||||
|
||||
|
||||
# === Rule: Private Static Readonly → PascalCase ===
|
||||
dotnet_naming_rule.private_static_readonly.severity = warning
|
||||
dotnet_naming_rule.private_static_readonly.symbols = private_static_readonly_symbols
|
||||
dotnet_naming_rule.private_static_readonly.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
|
||||
|
||||
|
||||
# ##############################################################
|
||||
# #
|
||||
# # JetBrains Rider / ReSharper Settings
|
||||
# #
|
||||
# ##############################################################
|
||||
|
||||
# === Brace-Style (für ReSharper-spezifische Formatierung) ===
|
||||
resharper_csharp_brace_style = next_line
|
||||
|
||||
# Kurze Statements ohne Klammern erlaubt (für 1-Zeiler)
|
||||
resharper_csharp_braces_for_foreach = not_required
|
||||
resharper_csharp_braces_for_for = not_required
|
||||
resharper_csharp_braces_for_while = not_required
|
||||
|
||||
# === Auto-Detection und Formatter-Tags ===
|
||||
resharper_autodetect_indent_settings = true
|
||||
resharper_use_indent_from_vs = false
|
||||
|
||||
# Erlaubt @formatter:off / @formatter:on Kommentare im Code
|
||||
resharper_formatter_off_tag = @formatter:off
|
||||
resharper_formatter_on_tag = @formatter:on
|
||||
resharper_formatter_tags_enabled = true
|
||||
|
||||
|
||||
# =====================================================
|
||||
# ReSharper Inspection Severities
|
||||
# (Hints = blaue Wellen, Warnings = gelb, Errors = rot)
|
||||
# =====================================================
|
||||
|
||||
# Style-Suggestions: nur als Hint anzeigen
|
||||
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||
resharper_arrange_this_qualifier_highlighting = hint
|
||||
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 = hint
|
||||
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
|
||||
|
||||
# Echte Probleme: als Warning
|
||||
resharper_redundant_base_qualifier_highlighting = warning
|
||||
+47
-16
@@ -1,19 +1,50 @@
|
||||
# Local development environment template
|
||||
#
|
||||
# Copy this file to `.env` and adjust paths to your setup,
|
||||
# or run: bash scripts/setup-dev-env.sh
|
||||
#
|
||||
# `.env` is gitignored — never commit your local paths.
|
||||
#
|
||||
# Activate in shell:
|
||||
# set -a; source .env; set +a
|
||||
#
|
||||
# Or use direnv (recommended):
|
||||
# echo 'dotenv .env' > .envrc && direnv allow
|
||||
##############################################################
|
||||
##
|
||||
## .env.example – Hellion Forge / Hellion Media
|
||||
##
|
||||
## Template für lokale Entwicklungsumgebung.
|
||||
## Kopiere diese Datei nach `.env` und passe die Pfade
|
||||
## an dein Setup an.
|
||||
##
|
||||
## ⚠️ `.env` ist gitignored – niemals lokale Pfade committen!
|
||||
##
|
||||
##############################################################
|
||||
##
|
||||
## SETUP
|
||||
##
|
||||
## 1) Manuell:
|
||||
## cp .env.example .env
|
||||
## # Pfade in .env anpassen
|
||||
##
|
||||
## 2) Automatisch:
|
||||
## bash scripts/setup-dev-env.sh
|
||||
##
|
||||
## AKTIVIERUNG IN DER SHELL
|
||||
##
|
||||
## Variante A – einmalig pro Shell:
|
||||
## set -a; source .env; set +a
|
||||
##
|
||||
## Variante B – mit direnv (empfohlen):
|
||||
## echo 'dotenv .env' > .envrc
|
||||
## direnv allow
|
||||
##
|
||||
##############################################################
|
||||
|
||||
# Path to Dalamud development DLLs (Dalamud.dll, FFXIVClientStructs.dll,
|
||||
# Lumina.dll, Lumina.Excel.dll). Required for building ChatTwo.Tests project.
|
||||
|
||||
# =====================================================
|
||||
# Build & Development Paths
|
||||
# =====================================================
|
||||
|
||||
# Pfad zu den Dalamud-Development-DLLs:
|
||||
# - Dalamud.dll
|
||||
# - FFXIVClientStructs.dll
|
||||
# - Lumina.dll
|
||||
# - Lumina.Excel.dll
|
||||
#
|
||||
# XIVLauncher Core (Linux): ~/.xlcore/dalamud/Hooks/dev
|
||||
# XIVLauncher (Windows): %AppData%\XIVLauncher\addon\Hooks\dev
|
||||
# Wird zum Bauen des HellionChat.Tests-Projekts benötigt.
|
||||
#
|
||||
# Standardpfade je nach Plattform:
|
||||
# XIVLauncher Core (Linux): ~/.xlcore/dalamud/Hooks/dev
|
||||
# XIVLauncher (Windows): %AppData%\XIVLauncher\addon\Hooks\dev
|
||||
# XIVLauncher (macOS): ~/Library/Application Support/XIV on Mac/dalamud/Hooks/dev
|
||||
DALAMUD_HOME=/path/to/dalamud/dev/dlls
|
||||
|
||||
+177
-7
@@ -1,8 +1,178 @@
|
||||
# Generated files
|
||||
HellionChat/Resources/Language.*.resx linguist-generated=true
|
||||
##############################################################
|
||||
##
|
||||
## .gitattributes – Hellion Forge / Hellion Media
|
||||
##
|
||||
## Setup: Linux-First Development
|
||||
## (Hauptentwicklung auf Linux, Target = Dalamud/Windows)
|
||||
## Überarbeitet: Mai 2026
|
||||
##
|
||||
## Strategie:
|
||||
## - Default: Alles LF (Linux-Konvention)
|
||||
## - Windows-Batch-Scripts: CRLF (technische Pflicht!)
|
||||
## - PowerShell: CRLF (Sicherheit für Windows PS 5.1)
|
||||
## - Binärdateien: explizit markiert (gegen Korruption)
|
||||
##
|
||||
## Hinweis:
|
||||
## Moderne Visual-Studio- und MSBuild-Versionen kommen
|
||||
## problemlos mit LF in .sln/.csproj klar.
|
||||
## Falls jemals Probleme auftauchen: hier umstellen.
|
||||
##
|
||||
##############################################################
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Default: Auto-Detect, alles auf LF normalisieren
|
||||
# =====================================================
|
||||
|
||||
* text=auto eol=lf
|
||||
*.cs text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.md text eol=lf
|
||||
*.json text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Source Code (LF)
|
||||
# =====================================================
|
||||
|
||||
*.cs text eol=lf
|
||||
*.csx text eol=lf
|
||||
*.vb text eol=lf
|
||||
*.fs text eol=lf
|
||||
*.fsx text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Configs & Daten (LF)
|
||||
# =====================================================
|
||||
|
||||
*.json text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.md text eol=lf
|
||||
*.txt text eol=lf
|
||||
*.config text eol=lf
|
||||
*.editorconfig text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.env.example text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Visual Studio / MSBuild Project Files (LF)
|
||||
# Linux-first: moderne Tools kommen mit LF zurecht
|
||||
# =====================================================
|
||||
|
||||
*.sln text eol=lf
|
||||
*.csproj text eol=lf
|
||||
*.vbproj text eol=lf
|
||||
*.fsproj text eol=lf
|
||||
*.props text eol=lf
|
||||
*.targets text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Resources & Lokalisierung (LF)
|
||||
# =====================================================
|
||||
|
||||
# Linguist soll generierte Sprachdateien nicht mitzählen
|
||||
HellionChat/Resources/Language.*.resx linguist-generated=true
|
||||
|
||||
*.resx text eol=lf
|
||||
*.resw text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Linux/Mac-Scripts (LF – Pflicht)
|
||||
# =====================================================
|
||||
|
||||
*.sh text eol=lf
|
||||
*.bash text eol=lf
|
||||
*.zsh text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# >>> AUSNAHMEN <<<
|
||||
# Windows-Scripts brauchen ZWINGEND CRLF.
|
||||
# Mit LF werden diese auf Windows nicht ausgeführt!
|
||||
# =====================================================
|
||||
|
||||
# Batch-Scripts (cmd.exe braucht CRLF)
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
|
||||
# PowerShell (PS 7+ wäre LF-tolerant,
|
||||
# aber Windows PowerShell 5.1 zickt teilweise)
|
||||
*.ps1 text eol=crlf
|
||||
*.psm1 text eol=crlf
|
||||
*.psd1 text eol=crlf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Binäre Build-Artefakte
|
||||
# =====================================================
|
||||
|
||||
*.dll binary
|
||||
*.exe binary
|
||||
*.pdb binary
|
||||
*.so binary
|
||||
*.dylib binary
|
||||
*.nupkg binary
|
||||
*.snupkg binary
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Bilder (binary)
|
||||
# =====================================================
|
||||
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.bmp binary
|
||||
*.tiff binary
|
||||
*.webp binary
|
||||
|
||||
# SVG ist eigentlich XML – als Text behandeln
|
||||
*.svg text eol=lf
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Fonts (binary)
|
||||
# =====================================================
|
||||
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.eot binary
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Archive (binary)
|
||||
# =====================================================
|
||||
|
||||
*.zip binary
|
||||
*.7z binary
|
||||
*.tar binary
|
||||
*.gz binary
|
||||
*.rar binary
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Audio / Video (binary)
|
||||
# =====================================================
|
||||
|
||||
*.wav binary
|
||||
*.mp3 binary
|
||||
*.ogg binary
|
||||
*.mp4 binary
|
||||
|
||||
|
||||
# =====================================================
|
||||
# FFXIV / Dalamud spezifische Binär-Formate
|
||||
# =====================================================
|
||||
|
||||
*.tex binary
|
||||
*.pap binary
|
||||
*.avfx binary
|
||||
*.shpk binary
|
||||
*.scd binary
|
||||
+27
-27
@@ -11,43 +11,43 @@ name: Build
|
||||
# Dalamud SDK 15 uses on Linux).
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Minimum permissions for a build-only workflow: read the repo, nothing
|
||||
# else. Closes the CodeQL "Workflow does not contain permissions" alert
|
||||
# and matches the principle-of-least-privilege the security guide
|
||||
# recommends for workflows that don't push or create releases.
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (Release)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
build:
|
||||
name: Build (Release)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
- name: Restore
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||
|
||||
+215
-215
@@ -17,234 +17,234 @@ name: Forge Announce
|
||||
# (issue titles, PR bodies, commit messages, etc.) flows into run-steps.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Existing tag to (re)post, e.g. v1.1.0"
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Existing tag to (re)post, e.g. v1.1.0'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
announce:
|
||||
name: Post changelog to Hellion Forge
|
||||
runs-on: ubuntu-latest
|
||||
# The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
|
||||
# on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
|
||||
# scope for every job by default, no environment: declaration needed.
|
||||
timeout-minutes: 5
|
||||
announce:
|
||||
name: Post changelog to Hellion Forge
|
||||
runs-on: ubuntu-latest
|
||||
# The DISCORD_FORGE_WEBHOOK secret is set as a repo-level Actions Secret
|
||||
# on Gitea (Settings → Actions → Secrets). Repo-level secrets are in
|
||||
# scope for every job by default, no environment: declaration needed.
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
# On push:tags github.ref points at the tag commit; on workflow_dispatch
|
||||
# the user supplies the tag explicitly. Always check out that tag so
|
||||
# the yaml + forge-posts file are read from the tagged tree, not main.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
steps:
|
||||
# On push:tags github.ref points at the tag commit; on workflow_dispatch
|
||||
# the user supplies the tag explicitly. Always check out that tag so
|
||||
# the yaml + forge-posts file are read from the tagged tree, not main.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
# Build embed-payload as a JSON file on disk. PowerShell-Core (pwsh)
|
||||
# ships pre-installed on ubuntu-latest so we get the same scripting
|
||||
# patterns release.yml uses on windows-latest. Tag is read via env: to
|
||||
# treat it as a string variable rather than inline shell text, and
|
||||
# validated against the semver regex before any interpolation.
|
||||
- name: Build embed payload
|
||||
id: build
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
run: |
|
||||
$tag = $env:TAG_NAME
|
||||
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
|
||||
throw "V1: Refusing to announce non-semver tag: $tag"
|
||||
# Build embed-payload as a JSON file on disk. PowerShell-Core (pwsh)
|
||||
# ships pre-installed on ubuntu-latest so we get the same scripting
|
||||
# patterns release.yml uses on windows-latest. Tag is read via env: to
|
||||
# treat it as a string variable rather than inline shell text, and
|
||||
# validated against the semver regex before any interpolation.
|
||||
- name: Build embed payload
|
||||
id: build
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
run: |
|
||||
$tag = $env:TAG_NAME
|
||||
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
|
||||
throw "V1: Refusing to announce non-semver tag: $tag"
|
||||
}
|
||||
$version = $tag.Substring(1)
|
||||
|
||||
# ---------- Forge-Post-Datei lesen ----------
|
||||
$forgePath = ".github/forge-posts/$tag.md"
|
||||
if (-not (Test-Path $forgePath)) {
|
||||
throw "V2: Forge-Post-Datei für $tag fehlt unter .github/forge-posts/. Datei vor dem Tag anlegen, dann Tag re-pushen oder workflow_dispatch."
|
||||
}
|
||||
$forgeRaw = Get-Content -Path $forgePath -Raw
|
||||
|
||||
# Frontmatter (--- … ---) am Datei-Anfang
|
||||
if ($forgeRaw -notmatch '(?s)\A---\s*\r?\n(.*?)\r?\n---\s*\r?\n(.*)\z') {
|
||||
throw "V3: Frontmatter (---) fehlt oder ist defekt in $forgePath"
|
||||
}
|
||||
$fmText = $matches[1]
|
||||
$deBody = $matches[2].Trim()
|
||||
|
||||
$subtitle = $null
|
||||
$versionsnatur = $null
|
||||
foreach ($line in ($fmText -split "`r?`n")) {
|
||||
if ($line -match '^subtitle:\s*"?([^"]*)"?\s*$') { $subtitle = $matches[1] }
|
||||
if ($line -match '^versionsnatur:\s*"?([^"]*)"?\s*$') { $versionsnatur = $matches[1] }
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($subtitle)) { throw "V3: Frontmatter-Feld 'subtitle' fehlt in $forgePath" }
|
||||
if ([string]::IsNullOrWhiteSpace($versionsnatur)) { throw "V3: Frontmatter-Feld 'versionsnatur' fehlt in $forgePath" }
|
||||
if ($subtitle.Length -gt 60) { throw "V4: Frontmatter-Feld 'subtitle' überschreitet Limit ($($subtitle.Length) Char, max 60)" }
|
||||
if ($versionsnatur.Length -gt 40) { throw "V4: Frontmatter-Feld 'versionsnatur' überschreitet Limit ($($versionsnatur.Length) Char, max 40)" }
|
||||
if ([string]::IsNullOrWhiteSpace($deBody)) { throw "V3: DE-Body fehlt in $forgePath" }
|
||||
|
||||
# ---------- EN-Block aus HellionChat.yaml ziehen ----------
|
||||
# 1:1 Pattern aus release.yml — gleicher Header-Marker, gleiches
|
||||
# Trailer-Verhalten. Bei Drift die zwei Workflows synchron halten.
|
||||
$yamlPath = "HellionChat/HellionChat.yaml"
|
||||
$raw = Get-Content -Path $yamlPath -Raw
|
||||
$marker = "changelog: |-"
|
||||
$idx = $raw.IndexOf($marker)
|
||||
if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" }
|
||||
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||
if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
|
||||
}) -join "`n"
|
||||
|
||||
$header = "**v$version "
|
||||
$start = $changelogBody.IndexOf($header)
|
||||
if ($start -lt 0) {
|
||||
throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging."
|
||||
}
|
||||
$rest = $changelogBody.Substring($start)
|
||||
$nextHdr = $rest.IndexOf("`n`n**v", 1)
|
||||
$trailer = $rest.IndexOf("`n`n---")
|
||||
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
||||
$enBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
||||
} elseif ($trailer -ge 0) {
|
||||
$enBlock = $rest.Substring(0, $trailer).TrimEnd()
|
||||
} else {
|
||||
$enBlock = $rest.TrimEnd()
|
||||
}
|
||||
|
||||
# ---------- Embed-Felder + Per-Field-Caps (Discord-Hard-Limits) ----------
|
||||
# Discord enforces per-embed-field limits separately from the
|
||||
# combined-total limit. We split the DE and EN blocks into two
|
||||
# embeds that share the same release URL so Discord stitches
|
||||
# them into one visual card. Hard caps per Discord docs:
|
||||
# description: 4096 per embed
|
||||
# title: 256 per embed
|
||||
# footer.text: 2048 per embed
|
||||
# combined sum across all embeds: 6000
|
||||
$title = "Hellion Chat $version — $subtitle"
|
||||
$deDesc = "**Deutsch**`n`n$deBody"
|
||||
$enDesc = "**English**`n`n$enBlock"
|
||||
$footerText = "Hellion Forge · $versionsnatur"
|
||||
$releaseUrl = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
||||
|
||||
if ($deDesc.Length -gt 4096) {
|
||||
throw "V6a: DE-Body too long for one embed ($($deDesc.Length) chars, max 4096). Trim .github/forge-posts/$tag.md or post the announcement manually (see forge style §8)."
|
||||
}
|
||||
if ($enDesc.Length -gt 4096) {
|
||||
throw "V6b: EN-Block too long for one embed ($($enDesc.Length) chars, max 4096). Trim the changelog entry in HellionChat/HellionChat.yaml or post manually."
|
||||
}
|
||||
$totalChars = $title.Length + $deDesc.Length + $enDesc.Length + $footerText.Length
|
||||
if ($totalChars -gt 6000) {
|
||||
throw "V6c: Combined embed chars $totalChars exceed Discord's 6000-total limit. Major-Release detected — post manually via Bot/Multi-Embed (see forge style §8)."
|
||||
}
|
||||
Write-Host "Embed-Caps OK: de=$($deDesc.Length)/4096, en=$($enDesc.Length)/4096, total=$totalChars/6000"
|
||||
|
||||
# ---------- Embed-Payload bauen (zwei Embeds, gleiche url) ----------
|
||||
# Sharing the same `url` tells Discord to render both embeds as a
|
||||
# single contiguous card block. The title sits on the first embed,
|
||||
# the footer + timestamp on the last so it reads as one post.
|
||||
$payload = [ordered]@{
|
||||
username = "Forge Herald"
|
||||
avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
|
||||
content = "<@&1500489631555260446>"
|
||||
allowed_mentions = [ordered]@{
|
||||
parse = @()
|
||||
roles = @("1500489631555260446")
|
||||
}
|
||||
embeds = @(
|
||||
[ordered]@{
|
||||
title = $title
|
||||
url = $releaseUrl
|
||||
color = 12730636
|
||||
description = $deDesc
|
||||
},
|
||||
[ordered]@{
|
||||
url = $releaseUrl
|
||||
color = 12730636
|
||||
description = $enDesc
|
||||
footer = [ordered]@{ text = $footerText }
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
}
|
||||
$version = $tag.Substring(1)
|
||||
)
|
||||
}
|
||||
|
||||
# ---------- Forge-Post-Datei lesen ----------
|
||||
$forgePath = ".github/forge-posts/$tag.md"
|
||||
if (-not (Test-Path $forgePath)) {
|
||||
throw "V2: Forge-Post-Datei für $tag fehlt unter .github/forge-posts/. Datei vor dem Tag anlegen, dann Tag re-pushen oder workflow_dispatch."
|
||||
}
|
||||
$forgeRaw = Get-Content -Path $forgePath -Raw
|
||||
$payloadJson = $payload | ConvertTo-Json -Depth 8 -Compress
|
||||
# Ausgabe-Datei ohne trailing newline für sauberes curl --data-binary @-
|
||||
[System.IO.File]::WriteAllText("$PWD/embed-payload.json", $payloadJson, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
# Frontmatter (--- … ---) am Datei-Anfang
|
||||
if ($forgeRaw -notmatch '(?s)\A---\s*\r?\n(.*?)\r?\n---\s*\r?\n(.*)\z') {
|
||||
throw "V3: Frontmatter (---) fehlt oder ist defekt in $forgePath"
|
||||
}
|
||||
$fmText = $matches[1]
|
||||
$deBody = $matches[2].Trim()
|
||||
Write-Host "Payload size: $($payloadJson.Length) chars"
|
||||
Write-Host "Embed title: $title"
|
||||
Write-Host "Embed footer: $footerText"
|
||||
|
||||
$subtitle = $null
|
||||
$versionsnatur = $null
|
||||
foreach ($line in ($fmText -split "`r?`n")) {
|
||||
if ($line -match '^subtitle:\s*"?([^"]*)"?\s*$') { $subtitle = $matches[1] }
|
||||
if ($line -match '^versionsnatur:\s*"?([^"]*)"?\s*$') { $versionsnatur = $matches[1] }
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($subtitle)) { throw "V3: Frontmatter-Feld 'subtitle' fehlt in $forgePath" }
|
||||
if ([string]::IsNullOrWhiteSpace($versionsnatur)) { throw "V3: Frontmatter-Feld 'versionsnatur' fehlt in $forgePath" }
|
||||
if ($subtitle.Length -gt 60) { throw "V4: Frontmatter-Feld 'subtitle' überschreitet Limit ($($subtitle.Length) Char, max 60)" }
|
||||
if ($versionsnatur.Length -gt 40) { throw "V4: Frontmatter-Feld 'versionsnatur' überschreitet Limit ($($versionsnatur.Length) Char, max 40)" }
|
||||
if ([string]::IsNullOrWhiteSpace($deBody)) { throw "V3: DE-Body fehlt in $forgePath" }
|
||||
# POST to the Hellion Forge changelog webhook. curl from PowerShell-Core
|
||||
# so we can pipe the payload via stdin (--data-binary @-) and keep
|
||||
# secrets out of process arg lists. One retry on 5xx, hard fail on 4xx.
|
||||
- name: POST to Hellion Forge webhook
|
||||
shell: pwsh
|
||||
env:
|
||||
DISCORD_FORGE_WEBHOOK: ${{ secrets.DISCORD_FORGE_WEBHOOK }}
|
||||
run: |
|
||||
if ([string]::IsNullOrEmpty($env:DISCORD_FORGE_WEBHOOK)) {
|
||||
throw "V7: DISCORD_FORGE_WEBHOOK secret is empty. Check Settings → Environments → Webhook."
|
||||
}
|
||||
|
||||
# ---------- EN-Block aus HellionChat.yaml ziehen ----------
|
||||
# 1:1 Pattern aus release.yml — gleicher Header-Marker, gleiches
|
||||
# Trailer-Verhalten. Bei Drift die zwei Workflows synchron halten.
|
||||
$yamlPath = "HellionChat/HellionChat.yaml"
|
||||
$raw = Get-Content -Path $yamlPath -Raw
|
||||
$marker = "changelog: |-"
|
||||
$idx = $raw.IndexOf($marker)
|
||||
if ($idx -lt 0) { throw "V5: changelog-Block nicht gefunden in $yamlPath" }
|
||||
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||
if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
|
||||
}) -join "`n"
|
||||
$payloadFile = "$PWD/embed-payload.json"
|
||||
if (-not (Test-Path $payloadFile)) {
|
||||
throw "Embed payload file missing — previous step did not produce embed-payload.json"
|
||||
}
|
||||
|
||||
$header = "**v$version "
|
||||
$start = $changelogBody.IndexOf($header)
|
||||
if ($start -lt 0) {
|
||||
throw "V5: No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging."
|
||||
}
|
||||
$rest = $changelogBody.Substring($start)
|
||||
$nextHdr = $rest.IndexOf("`n`n**v", 1)
|
||||
$trailer = $rest.IndexOf("`n`n---")
|
||||
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
||||
$enBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
||||
} elseif ($trailer -ge 0) {
|
||||
$enBlock = $rest.Substring(0, $trailer).TrimEnd()
|
||||
} else {
|
||||
$enBlock = $rest.TrimEnd()
|
||||
}
|
||||
$maxAttempts = 2
|
||||
$attempt = 0
|
||||
while ($attempt -lt $maxAttempts) {
|
||||
$attempt++
|
||||
Write-Host "POST attempt $attempt of $maxAttempts"
|
||||
$tmpResp = "$PWD/.webhook-response"
|
||||
$tmpHeaders = "$PWD/.webhook-headers"
|
||||
# --silent suppresses progress; --show-error prints errors so
|
||||
# the workflow log shows what happened. -w prints HTTP status
|
||||
# to stdout for inspection. -o captures body for diagnosis,
|
||||
# -D captures headers.
|
||||
$rawStatus = Get-Content $payloadFile -Raw |
|
||||
curl --silent --show-error `
|
||||
--header 'Content-Type: application/json' `
|
||||
--data-binary '@-' `
|
||||
-D $tmpHeaders `
|
||||
-o $tmpResp `
|
||||
-w '%{http_code}' `
|
||||
"$env:DISCORD_FORGE_WEBHOOK"
|
||||
$status = [int]$rawStatus
|
||||
Write-Host "HTTP status: $status"
|
||||
|
||||
# ---------- Embed-Felder + Per-Field-Caps (Discord-Hard-Limits) ----------
|
||||
# Discord enforces per-embed-field limits separately from the
|
||||
# combined-total limit. We split the DE and EN blocks into two
|
||||
# embeds that share the same release URL so Discord stitches
|
||||
# them into one visual card. Hard caps per Discord docs:
|
||||
# description: 4096 per embed
|
||||
# title: 256 per embed
|
||||
# footer.text: 2048 per embed
|
||||
# combined sum across all embeds: 6000
|
||||
$title = "Hellion Chat $version — $subtitle"
|
||||
$deDesc = "**Deutsch**`n`n$deBody"
|
||||
$enDesc = "**English**`n`n$enBlock"
|
||||
$footerText = "Hellion Forge · $versionsnatur"
|
||||
$releaseUrl = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
||||
if ($status -ge 200 -and $status -lt 300) {
|
||||
Write-Host "Forge announce POST succeeded."
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($deDesc.Length -gt 4096) {
|
||||
throw "V6a: DE-Body too long for one embed ($($deDesc.Length) chars, max 4096). Trim .github/forge-posts/$tag.md or post the announcement manually (see forge style §8)."
|
||||
}
|
||||
if ($enDesc.Length -gt 4096) {
|
||||
throw "V6b: EN-Block too long for one embed ($($enDesc.Length) chars, max 4096). Trim the changelog entry in HellionChat/HellionChat.yaml or post manually."
|
||||
}
|
||||
$totalChars = $title.Length + $deDesc.Length + $enDesc.Length + $footerText.Length
|
||||
if ($totalChars -gt 6000) {
|
||||
throw "V6c: Combined embed chars $totalChars exceed Discord's 6000-total limit. Major-Release detected — post manually via Bot/Multi-Embed (see forge style §8)."
|
||||
}
|
||||
Write-Host "Embed-Caps OK: de=$($deDesc.Length)/4096, en=$($enDesc.Length)/4096, total=$totalChars/6000"
|
||||
$bodySnippet = ""
|
||||
if (Test-Path $tmpResp) {
|
||||
$bodySnippet = (Get-Content $tmpResp -Raw -ErrorAction SilentlyContinue)
|
||||
if ($bodySnippet.Length -gt 500) { $bodySnippet = $bodySnippet.Substring(0, 500) + " …" }
|
||||
}
|
||||
|
||||
# ---------- Embed-Payload bauen (zwei Embeds, gleiche url) ----------
|
||||
# Sharing the same `url` tells Discord to render both embeds as a
|
||||
# single contiguous card block. The title sits on the first embed,
|
||||
# the footer + timestamp on the last so it reads as one post.
|
||||
$payload = [ordered]@{
|
||||
username = "Forge Herald"
|
||||
avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
|
||||
content = "<@&1500489631555260446>"
|
||||
allowed_mentions = [ordered]@{
|
||||
parse = @()
|
||||
roles = @("1500489631555260446")
|
||||
}
|
||||
embeds = @(
|
||||
[ordered]@{
|
||||
title = $title
|
||||
url = $releaseUrl
|
||||
color = 12730636
|
||||
description = $deDesc
|
||||
},
|
||||
[ordered]@{
|
||||
url = $releaseUrl
|
||||
color = 12730636
|
||||
description = $enDesc
|
||||
footer = [ordered]@{ text = $footerText }
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
}
|
||||
)
|
||||
}
|
||||
if ($status -ge 400 -and $status -lt 500) {
|
||||
# E2: 4xx is permanent — webhook revoked, channel deleted,
|
||||
# payload malformed. No retry.
|
||||
throw "E2: Discord-Webhook returned permanent $status. Body: $bodySnippet"
|
||||
}
|
||||
|
||||
$payloadJson = $payload | ConvertTo-Json -Depth 8 -Compress
|
||||
# Ausgabe-Datei ohne trailing newline für sauberes curl --data-binary @-
|
||||
[System.IO.File]::WriteAllText("$PWD/embed-payload.json", $payloadJson, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
Write-Host "Payload size: $($payloadJson.Length) chars"
|
||||
Write-Host "Embed title: $title"
|
||||
Write-Host "Embed footer: $footerText"
|
||||
|
||||
# POST to the Hellion Forge changelog webhook. curl from PowerShell-Core
|
||||
# so we can pipe the payload via stdin (--data-binary @-) and keep
|
||||
# secrets out of process arg lists. One retry on 5xx, hard fail on 4xx.
|
||||
- name: POST to Hellion Forge webhook
|
||||
shell: pwsh
|
||||
env:
|
||||
DISCORD_FORGE_WEBHOOK: ${{ secrets.DISCORD_FORGE_WEBHOOK }}
|
||||
run: |
|
||||
if ([string]::IsNullOrEmpty($env:DISCORD_FORGE_WEBHOOK)) {
|
||||
throw "V7: DISCORD_FORGE_WEBHOOK secret is empty. Check Settings → Environments → Webhook."
|
||||
}
|
||||
|
||||
$payloadFile = "$PWD/embed-payload.json"
|
||||
if (-not (Test-Path $payloadFile)) {
|
||||
throw "Embed payload file missing — previous step did not produce embed-payload.json"
|
||||
}
|
||||
|
||||
$maxAttempts = 2
|
||||
$attempt = 0
|
||||
while ($attempt -lt $maxAttempts) {
|
||||
$attempt++
|
||||
Write-Host "POST attempt $attempt of $maxAttempts"
|
||||
$tmpResp = "$PWD/.webhook-response"
|
||||
$tmpHeaders = "$PWD/.webhook-headers"
|
||||
# --silent suppresses progress; --show-error prints errors so
|
||||
# the workflow log shows what happened. -w prints HTTP status
|
||||
# to stdout for inspection. -o captures body for diagnosis,
|
||||
# -D captures headers.
|
||||
$rawStatus = Get-Content $payloadFile -Raw |
|
||||
curl --silent --show-error `
|
||||
--header 'Content-Type: application/json' `
|
||||
--data-binary '@-' `
|
||||
-D $tmpHeaders `
|
||||
-o $tmpResp `
|
||||
-w '%{http_code}' `
|
||||
"$env:DISCORD_FORGE_WEBHOOK"
|
||||
$status = [int]$rawStatus
|
||||
Write-Host "HTTP status: $status"
|
||||
|
||||
if ($status -ge 200 -and $status -lt 300) {
|
||||
Write-Host "Forge announce POST succeeded."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$bodySnippet = ""
|
||||
if (Test-Path $tmpResp) {
|
||||
$bodySnippet = (Get-Content $tmpResp -Raw -ErrorAction SilentlyContinue)
|
||||
if ($bodySnippet.Length -gt 500) { $bodySnippet = $bodySnippet.Substring(0, 500) + " …" }
|
||||
}
|
||||
|
||||
if ($status -ge 400 -and $status -lt 500) {
|
||||
# E2: 4xx is permanent — webhook revoked, channel deleted,
|
||||
# payload malformed. No retry.
|
||||
throw "E2: Discord-Webhook returned permanent $status. Body: $bodySnippet"
|
||||
}
|
||||
|
||||
# E1: 5xx (or transport-level fail with status 0) — wait + retry once
|
||||
if ($attempt -lt $maxAttempts) {
|
||||
Write-Host "Transient $status — sleeping 30s before retry."
|
||||
Start-Sleep -Seconds 30
|
||||
} else {
|
||||
throw "E1: Discord-Webhook returned transient $status after $maxAttempts attempts. Body: $bodySnippet"
|
||||
}
|
||||
}
|
||||
# E1: 5xx (or transport-level fail with status 0) — wait + retry once
|
||||
if ($attempt -lt $maxAttempts) {
|
||||
Write-Host "Transient $status — sleeping 30s before retry."
|
||||
Start-Sleep -Seconds 30
|
||||
} else {
|
||||
throw "E1: Discord-Webhook returned transient $status after $maxAttempts attempts. Body: $bodySnippet"
|
||||
}
|
||||
}
|
||||
|
||||
+142
-142
@@ -17,167 +17,167 @@ name: Release
|
||||
# Linux when the Dalamud staging assemblies sit under $(HOME)/.xlcore/...
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
# Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
|
||||
# tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
|
||||
# ref step below hard-fails if a non-tag ref is selected, because the
|
||||
# release-action reads GITHUB_REF directly and rejects anything that
|
||||
# does not start with refs/tags/.
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
# Manual recovery trigger. Use Gitea's "Run workflow" UI and select the
|
||||
# tag (e.g. v1.4.4) from the Ref dropdown - not main. The Validate tag
|
||||
# ref step below hard-fails if a non-tag ref is selected, because the
|
||||
# release-action reads GITHUB_REF directly and rejects anything that
|
||||
# does not start with refs/tags/.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and attach release ZIP
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
release:
|
||||
name: Build and attach release ZIP
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
# release-action@main reads GITHUB_REF directly (its action.yml
|
||||
# does not declare a tag_name input). Validate up-front so manual
|
||||
# dispatches from a branch ref fail loud here instead of burning
|
||||
# a full build before the final step errors out with "ref X is
|
||||
# not a tag".
|
||||
- name: Validate tag ref
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
|
||||
echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}"
|
||||
echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown."
|
||||
exit 1
|
||||
fi
|
||||
steps:
|
||||
# release-action@main reads GITHUB_REF directly (its action.yml
|
||||
# does not declare a tag_name input). Validate up-front so manual
|
||||
# dispatches from a branch ref fail loud here instead of burning
|
||||
# a full build before the final step errors out with "ref X is
|
||||
# not a tag".
|
||||
- name: Validate tag ref
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
|
||||
echo "::error::Release workflow must run on a v*.X.Y tag ref, got ${GITHUB_REF}"
|
||||
echo "::error::Push a tag, or pick the tag (not main) in the workflow_dispatch Ref dropdown."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
- name: Download Dalamud staging
|
||||
run: |
|
||||
hooks="$HOME/.xlcore/dalamud/Hooks/dev"
|
||||
mkdir -p "$hooks"
|
||||
curl -fsSL https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -o dalamud.zip
|
||||
unzip -oq dalamud.zip -d "$hooks"
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||
|
||||
- name: Locate latest.zip
|
||||
id: locate
|
||||
run: |
|
||||
zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
|
||||
if [ -z "$zip" ]; then
|
||||
echo "latest.zip not found under HellionChat/bin/Release" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Found: $zip"
|
||||
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
||||
- name: Locate latest.zip
|
||||
id: locate
|
||||
run: |
|
||||
zip="$(find HellionChat/bin/Release -name latest.zip -print -quit)"
|
||||
if [ -z "$zip" ]; then
|
||||
echo "latest.zip not found under HellionChat/bin/Release" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Found: $zip"
|
||||
echo "path=$zip" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Build a release body from the matching changelog block in
|
||||
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||
# workflow if no block exists for the tagged version, which is the
|
||||
# automated counterpart to the "yaml + repo.json + release body
|
||||
# kept in sync" rule.
|
||||
#
|
||||
# GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
|
||||
# tag value is treated as a PowerShell variable, not as inline shell
|
||||
# text. The strict regex below rejects anything that is not a clean
|
||||
# semver tag before it is used to build a string.
|
||||
- name: Generate release body
|
||||
shell: pwsh
|
||||
env:
|
||||
# github.ref_name is the tag because Validate tag ref above
|
||||
# already enforced refs/tags/v*. Read via env: so the value
|
||||
# is a PowerShell variable, not inline shell text, and gets
|
||||
# re-validated against the semver regex below.
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
$tag = $env:TAG_NAME
|
||||
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
|
||||
throw "Refusing to generate release body for non-semver tag: $tag"
|
||||
}
|
||||
$version = $tag.Substring(1)
|
||||
# Build a release body from the matching changelog block in
|
||||
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||
# workflow if no block exists for the tagged version, which is the
|
||||
# automated counterpart to the "yaml + repo.json + release body
|
||||
# kept in sync" rule.
|
||||
#
|
||||
# GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
|
||||
# tag value is treated as a PowerShell variable, not as inline shell
|
||||
# text. The strict regex below rejects anything that is not a clean
|
||||
# semver tag before it is used to build a string.
|
||||
- name: Generate release body
|
||||
shell: pwsh
|
||||
env:
|
||||
# github.ref_name is the tag because Validate tag ref above
|
||||
# already enforced refs/tags/v*. Read via env: so the value
|
||||
# is a PowerShell variable, not inline shell text, and gets
|
||||
# re-validated against the semver regex below.
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
$tag = $env:TAG_NAME
|
||||
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
|
||||
throw "Refusing to generate release body for non-semver tag: $tag"
|
||||
}
|
||||
$version = $tag.Substring(1)
|
||||
|
||||
$yamlPath = "HellionChat/HellionChat.yaml"
|
||||
$raw = Get-Content -Path $yamlPath -Raw
|
||||
$yamlPath = "HellionChat/HellionChat.yaml"
|
||||
$raw = Get-Content -Path $yamlPath -Raw
|
||||
|
||||
$marker = "changelog: |-"
|
||||
$idx = $raw.IndexOf($marker)
|
||||
if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
|
||||
$marker = "changelog: |-"
|
||||
$idx = $raw.IndexOf($marker)
|
||||
if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
|
||||
|
||||
# changelog: is the last top-level key in the manifest, so
|
||||
# everything after the marker is the literal block. Strip the
|
||||
# 4-space yaml indent (prettier convention) from each line.
|
||||
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||
if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
|
||||
}) -join "`n"
|
||||
# changelog: is the last top-level key in the manifest, so
|
||||
# everything after the marker is the literal block. Strip the
|
||||
# 4-space yaml indent (prettier convention) from each line.
|
||||
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||
if ($_ -match '^ ') { $_.Substring(4) } else { $_ }
|
||||
}) -join "`n"
|
||||
|
||||
# Subblock convention: "**vX.Y.Z — <subtitle> (<date>)**"
|
||||
# matches verify-changelog-sync.sh and slim-rule grep.
|
||||
$header = "**v$version "
|
||||
$start = $changelogBody.IndexOf($header)
|
||||
if ($start -lt 0) {
|
||||
throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
|
||||
}
|
||||
# Subblock convention: "**vX.Y.Z — <subtitle> (<date>)**"
|
||||
# matches verify-changelog-sync.sh and slim-rule grep.
|
||||
$header = "**v$version "
|
||||
$start = $changelogBody.IndexOf($header)
|
||||
if ($start -lt 0) {
|
||||
throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
|
||||
}
|
||||
|
||||
$rest = $changelogBody.Substring($start)
|
||||
$nextHdr = $rest.IndexOf("`n`n**v", 1)
|
||||
$trailer = $rest.IndexOf("`n`n---")
|
||||
$rest = $changelogBody.Substring($start)
|
||||
$nextHdr = $rest.IndexOf("`n`n**v", 1)
|
||||
$trailer = $rest.IndexOf("`n`n---")
|
||||
|
||||
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
||||
$currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
||||
} elseif ($trailer -ge 0) {
|
||||
$currentBlock = $rest.Substring(0, $trailer).TrimEnd()
|
||||
} else {
|
||||
$currentBlock = $rest.TrimEnd()
|
||||
}
|
||||
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
||||
$currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
||||
} elseif ($trailer -ge 0) {
|
||||
$currentBlock = $rest.Substring(0, $trailer).TrimEnd()
|
||||
} else {
|
||||
$currentBlock = $rest.TrimEnd()
|
||||
}
|
||||
|
||||
# Static install / docs / licence footer is maintained as a
|
||||
# separate file so the workflow YAML stays clean (no embedded
|
||||
# heredoc that would have to be indented under the run-block).
|
||||
$footerPath = ".github/release-footer.md"
|
||||
if (-not (Test-Path $footerPath)) {
|
||||
throw "Release footer template not found: $footerPath"
|
||||
}
|
||||
$footer = Get-Content -Path $footerPath -Raw
|
||||
# Static install / docs / licence footer is maintained as a
|
||||
# separate file so the workflow YAML stays clean (no embedded
|
||||
# heredoc that would have to be indented under the run-block).
|
||||
$footerPath = ".github/release-footer.md"
|
||||
if (-not (Test-Path $footerPath)) {
|
||||
throw "Release footer template not found: $footerPath"
|
||||
}
|
||||
$footer = Get-Content -Path $footerPath -Raw
|
||||
|
||||
$body = $currentBlock + "`n" + $footer
|
||||
$body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
|
||||
$body = $currentBlock + "`n" + $footer
|
||||
$body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
|
||||
|
||||
Write-Host "Generated release body for $tag :"
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host $body
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host "Generated release body for $tag :"
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host $body
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
# release-action@main only declares files/title/body/pre_release/
|
||||
# draft/api_key/insecure as inputs (see its action.yml). It silently
|
||||
# ignores anything else, including body_path and tag_name. The tag
|
||||
# itself comes from GITHUB_REF, the body must be passed inline via
|
||||
# body:, so we re-emit release-body.md as a step output first.
|
||||
- name: Expose release body for release-action
|
||||
id: body
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo 'content<<RELEASE_BODY_EOF'
|
||||
cat release-body.md
|
||||
echo 'RELEASE_BODY_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
# release-action@main only declares files/title/body/pre_release/
|
||||
# draft/api_key/insecure as inputs (see its action.yml). It silently
|
||||
# ignores anything else, including body_path and tag_name. The tag
|
||||
# itself comes from GITHUB_REF, the body must be passed inline via
|
||||
# body:, so we re-emit release-body.md as a step output first.
|
||||
- name: Expose release body for release-action
|
||||
id: body
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo 'content<<RELEASE_BODY_EOF'
|
||||
cat release-body.md
|
||||
echo 'RELEASE_BODY_EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Gitea-native release action. Creates the release if the tag has no
|
||||
# release yet, or updates the existing one with latest.zip attached
|
||||
# and the generated body. The auto-injected GITHUB_TOKEN on Gitea
|
||||
# Actions has Gitea-API scope and is sufficient for release write.
|
||||
- name: Attach to Gitea release
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: ${{ steps.locate.outputs.path }}
|
||||
body: ${{ steps.body.outputs.content }}
|
||||
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Gitea-native release action. Creates the release if the tag has no
|
||||
# release yet, or updates the existing one with latest.zip attached
|
||||
# and the generated body. The auto-injected GITHUB_TOKEN on Gitea
|
||||
# Actions has Gitea-API scope and is sufficient for release write.
|
||||
- name: Attach to Gitea release
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
files: ${{ steps.locate.outputs.path }}
|
||||
body: ${{ steps.body.outputs.content }}
|
||||
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
name: Security
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 6 * * 1"
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
|
||||
with:
|
||||
# MessageStore.cs uses string-interpolation in CommandText for table
|
||||
# names and clause-joins that come from internal code constants, not
|
||||
# user input. Values are bound via SqlParameter, the SQL surface is
|
||||
# local-only inside a Dalamud plugin. Semgrep matches the pattern
|
||||
# without dataflow, so it flags those eight call sites; CodeQL
|
||||
# would not. Suppressed for this repo only.
|
||||
semgrep-exclude-rules: "csharp.lang.security.sqli.csharp-sqli.csharp-sqli"
|
||||
scan:
|
||||
uses: JonKazama-Hellion/security-workflows/.gitea/workflows/security-scan.yml@main
|
||||
with:
|
||||
# MessageStore.cs uses string-interpolation in CommandText for table
|
||||
# names and clause-joins that come from internal code constants, not
|
||||
# user input. Values are bound via SqlParameter, the SQL surface is
|
||||
# local-only inside a Dalamud plugin. Semgrep matches the pattern
|
||||
# without dataflow, so it flags those eight call sites; CodeQL
|
||||
# would not. Suppressed for this repo only.
|
||||
semgrep-exclude-rules: 'csharp.lang.security.sqli.csharp-sqli.csharp-sqli'
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
name: Bug report
|
||||
description: Something in HellionChat is broken or behaves wrong
|
||||
labels:
|
||||
- bug
|
||||
- bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting. Please fill in the fields below so I can
|
||||
reproduce the issue. If this is a security issue, stop here and
|
||||
report it privately to [kontakt@hellion-media.de](mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D)
|
||||
instead.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting. Please fill in the fields below so I can
|
||||
reproduce the issue. If this is a security issue, stop here and
|
||||
report it privately to [kontakt@hellion-media.de](mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D)
|
||||
instead.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: HellionChat version
|
||||
description: From Settings → Information → Version
|
||||
placeholder: "0.5.4"
|
||||
validations:
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: HellionChat version
|
||||
description: From Settings → Information → Version
|
||||
placeholder: '0.5.4'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
options:
|
||||
- Windows (XIVLauncher)
|
||||
- Linux (XIVLauncher Core)
|
||||
- macOS (XIVLauncher Core / wine)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened
|
||||
description: Plain description, no log dumps yet
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What you expected
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: Step-by-step from "open settings" or "log in" through to the broken behaviour
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Relevant /xllog excerpt
|
||||
description: Filter for "HellionChat" if the log is huge
|
||||
render: text
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm
|
||||
attributes:
|
||||
label: Pre-flight
|
||||
options:
|
||||
- label: I am running the latest version of HellionChat
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
options:
|
||||
- Windows (XIVLauncher)
|
||||
- Linux (XIVLauncher Core)
|
||||
- macOS (XIVLauncher Core / wine)
|
||||
- Other
|
||||
validations:
|
||||
- label: I have searched existing issues for duplicates
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened
|
||||
description: Plain description, no log dumps yet
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: What you expected
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: Step-by-step from "open settings" or "log in" through to the broken behaviour
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Relevant /xllog excerpt
|
||||
description: Filter for "HellionChat" if the log is huge
|
||||
render: text
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm
|
||||
attributes:
|
||||
label: Pre-flight
|
||||
options:
|
||||
- label: I am running the latest version of HellionChat
|
||||
required: true
|
||||
- label: I have searched existing issues for duplicates
|
||||
required: true
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Security vulnerability
|
||||
url: mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
|
||||
about: Do not open a public issue for security problems. Report by e-mail instead.
|
||||
- name: Security vulnerability
|
||||
url: mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
|
||||
about: Do not open a public issue for security problems. Report by e-mail instead.
|
||||
|
||||
- name: Upstream Chat 2 issue
|
||||
url: https://github.com/Infiziert90/ChatTwo/issues
|
||||
about:
|
||||
If the issue exists in upstream Chat 2 too, please report it there so the original maintainers see it as well.
|
||||
- name: Upstream Chat 2 issue
|
||||
url: https://github.com/Infiziert90/ChatTwo/issues
|
||||
about:
|
||||
If the issue exists in upstream Chat 2 too, please report it there so the original maintainers see it as well.
|
||||
|
||||
- name: Discord
|
||||
url: https://discord.com/users/j.j_kazama
|
||||
about: Quick questions, casual feedback. Bug reports still go through the issue tracker for tracking.
|
||||
- name: Discord
|
||||
url: https://discord.com/users/j.j_kazama
|
||||
about: Quick questions, casual feedback. Bug reports still go through the issue tracker for tracking.
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature or enhancement for HellionChat
|
||||
labels:
|
||||
- enhancement
|
||||
- enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the suggestion. HellionChat focuses on privacy by
|
||||
default and a small, well-scoped feature set. Suggestions that
|
||||
align with that scope are easier to accept than ones that pull
|
||||
the plugin toward "do everything".
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the suggestion. HellionChat focuses on privacy by
|
||||
default and a small, well-scoped feature set. Suggestions that
|
||||
align with that scope are easier to accept than ones that pull
|
||||
the plugin toward "do everything".
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem are you trying to solve
|
||||
description: The user-side problem, not the proposed solution yet
|
||||
validations:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem are you trying to solve
|
||||
description: The user-side problem, not the proposed solution yet
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What you would like HellionChat to do
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives you have considered
|
||||
description: Other plugins, manual workarounds, settings combinations
|
||||
|
||||
- type: dropdown
|
||||
id: scope
|
||||
attributes:
|
||||
label: Scope estimate from your side
|
||||
options:
|
||||
- 'Small (one tab, one toggle, one filter)'
|
||||
- 'Medium (a settings section, persistent state, one new file)'
|
||||
- 'Large (architectural, touches the message pipeline or the database)'
|
||||
- "I don't know"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm
|
||||
attributes:
|
||||
label: Pre-flight
|
||||
options:
|
||||
- label: I have searched existing issues for similar requests
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What you would like HellionChat to do
|
||||
validations:
|
||||
- label: I understand HellionChat is a privacy-focused fork and not a feature parity tool with upstream Chat 2
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives you have considered
|
||||
description: Other plugins, manual workarounds, settings combinations
|
||||
|
||||
- type: dropdown
|
||||
id: scope
|
||||
attributes:
|
||||
label: Scope estimate from your side
|
||||
options:
|
||||
- "Small (one tab, one toggle, one filter)"
|
||||
- "Medium (a settings section, persistent state, one new file)"
|
||||
- "Large (architectural, touches the message pipeline or the database)"
|
||||
- "I don't know"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: confirm
|
||||
attributes:
|
||||
label: Pre-flight
|
||||
options:
|
||||
- label: I have searched existing issues for similar requests
|
||||
required: true
|
||||
- label:
|
||||
I understand HellionChat is a privacy-focused fork and not a feature parity tool with upstream Chat
|
||||
2
|
||||
required: true
|
||||
|
||||
@@ -18,7 +18,8 @@ mailto:kontakt@hellion-media.de?subject=%5BHellionChat%20Security%5D
|
||||
|
||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||
- [ ] New feature (non-breaking change that adds behaviour)
|
||||
- [ ] Breaking change (config migration, removed feature, or behaviour change that user-visible defaults rely on)
|
||||
- [ ] Breaking change (config migration, removed feature, or behaviour change that user-visible
|
||||
defaults rely on)
|
||||
- [ ] Documentation only
|
||||
- [ ] Translation update
|
||||
- [ ] Build, CI or tooling change
|
||||
@@ -55,10 +56,11 @@ new commands, new translations, removed behaviour. If none, write
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md).
|
||||
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and
|
||||
[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md).
|
||||
- [ ] My change matches the existing code style (`.editorconfig`).
|
||||
- [ ] I added or updated tests where the existing test infrastructure made that practical, or I have explained why tests
|
||||
are not applicable.
|
||||
- [ ] I added or updated tests where the existing test infrastructure made that practical, or I have
|
||||
explained why tests are not applicable.
|
||||
- [ ] I updated the README, in-plugin strings or documentation if my change is user-visible.
|
||||
- [ ] I did not include any AI-generated code without disclosing it in the PR description (see
|
||||
[AI_DISCLOSURE.md](../docs/AI_DISCLOSURE.md)).
|
||||
|
||||
+38
-38
@@ -1,42 +1,42 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
# NuGet package updates for the plugin project. Weekly cadence keeps the
|
||||
# noise down while still catching transitive security advisories within
|
||||
# a few days of disclosure.
|
||||
- package-ecosystem: nuget
|
||||
directory: /HellionChat
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "07:00"
|
||||
timezone: Europe/Berlin
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- dependencies
|
||||
- nuget
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
groups:
|
||||
patches:
|
||||
update-types:
|
||||
- patch
|
||||
minor:
|
||||
update-types:
|
||||
- minor
|
||||
# NuGet package updates for the plugin project. Weekly cadence keeps the
|
||||
# noise down while still catching transitive security advisories within
|
||||
# a few days of disclosure.
|
||||
- package-ecosystem: nuget
|
||||
directory: /HellionChat
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: '07:00'
|
||||
timezone: Europe/Berlin
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- dependencies
|
||||
- nuget
|
||||
commit-message:
|
||||
prefix: 'chore(deps)'
|
||||
groups:
|
||||
patches:
|
||||
update-types:
|
||||
- patch
|
||||
minor:
|
||||
update-types:
|
||||
- minor
|
||||
|
||||
# GitHub Actions versions in .github/workflows. Lower cadence because
|
||||
# Action releases ship less frequently and are usually safe to defer
|
||||
# for a month.
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "07:00"
|
||||
timezone: Europe/Berlin
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- dependencies
|
||||
- github-actions
|
||||
commit-message:
|
||||
prefix: "chore(actions)"
|
||||
# GitHub Actions versions in .github/workflows. Lower cadence because
|
||||
# Action releases ship less frequently and are usually safe to defer
|
||||
# for a month.
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: '07:00'
|
||||
timezone: Europe/Berlin
|
||||
open-pull-requests-limit: 3
|
||||
labels:
|
||||
- dependencies
|
||||
- github-actions
|
||||
commit-message:
|
||||
prefix: 'chore(actions)'
|
||||
|
||||
@@ -3,15 +3,16 @@ subtitle: "Theme Foundation"
|
||||
versionsnatur: "Major-UI-Cycle"
|
||||
---
|
||||
|
||||
- Theme-Engine mit fünf Built-In-Themes: Hellion Arctic (Default), Chat 2 Klassik, Event Horizon, Moonlit Bloom, Mint
|
||||
Grove
|
||||
- Settings öffnet jetzt eine Card-Grid-Übersicht — Klick auf eine Card führt in den Detail-View, Breadcrumb und ESC
|
||||
zurück zur Übersicht
|
||||
- Theme-Engine mit fünf Built-In-Themes: Hellion Arctic (Default), Chat 2 Klassik, Event Horizon,
|
||||
Moonlit Bloom, Mint Grove
|
||||
- Settings öffnet jetzt eine Card-Grid-Übersicht — Klick auf eine Card führt in den Detail-View,
|
||||
Breadcrumb und ESC zurück zur Übersicht
|
||||
- Themes-Tab mit Mini-Mockup pro Theme, Live-Switch beim Klick
|
||||
- Eigene Themes als JSON in `pluginConfigs/HellionChat/themes/` — Beispiel-Vorlage wird beim ersten Start automatisch
|
||||
abgelegt
|
||||
- Optional pro Theme eigene Chat-Channel-Farben mit Übernehmen/Behalten-Banner — niemals automatisch überschrieben
|
||||
- Eigene Themes als JSON in `pluginConfigs/HellionChat/themes/` — Beispiel-Vorlage wird beim ersten
|
||||
Start automatisch abgelegt
|
||||
- Optional pro Theme eigene Chat-Channel-Farben mit Übernehmen/Behalten-Banner — niemals automatisch
|
||||
überschrieben
|
||||
- Plugin-Icon zum Hellion-Forge-Hammer gewechselt
|
||||
- Migration v13 → v14: alle User landen auf Hellion Arctic. Wer den Upstream-Look will, wählt Chat 2 Klassik in Settings
|
||||
→ Themes
|
||||
- Migration v13 → v14: alle User landen auf Hellion Arctic. Wer den Upstream-Look will, wählt Chat 2
|
||||
Klassik in Settings → Themes
|
||||
- Anleitung zum Schreiben eigener Themes: `docs/THEME-AUTHORING.md`
|
||||
|
||||
@@ -3,22 +3,22 @@ subtitle: "Layout Refresh"
|
||||
versionsnatur: "Major-UI-Cycle"
|
||||
---
|
||||
|
||||
- Sidebar im neuen Look: fix 44 px breit, nur Icons, Tab-Name als Tooltip beim Hover, vertikale Akzent-Pill markiert den
|
||||
aktiven Tab
|
||||
- Sidebar im neuen Look: fix 44 px breit, nur Icons, Tab-Name als Tooltip beim Hover, vertikale
|
||||
Akzent-Pill markiert den aktiven Tab
|
||||
- Top-Tabs bekommen eine Akzent-Underline statt Background-Fill am aktiven Tab
|
||||
- Pro Tab eigenes Icon wählbar in Einstellungen → Tabs (FontAwesome-Pool)
|
||||
- Auto-Tell-Tabs sind jetzt visuell unterscheidbar: jeder Tell-Partner bekommt ein eigenes Icon
|
||||
(envelope/star/heart/bell/bookmark/flag/fire) plus eigene Farbe aus 12-Farb-Palette — 84 Kombinationen, gleicher
|
||||
Partner ergibt konsistent dieselbe
|
||||
- Pulsierender roter Dot oben rechts am Sidebar-Icon zeigt ungelesene Nachrichten an. Sanft, 2-Sekunden-Cycle,
|
||||
deaktivierbar über `Configuration.ReduceMotion` (UI-Toggle in v1.3.0)
|
||||
- Bottom-Status-Bar (22 px) mit fünf Live-Slots: aktiver Channel + Color-Dot, Privacy-Badge, Tab/Message-Counter,
|
||||
Auto-Tell-Counter, Plugin-Version. Update 1×/Sek
|
||||
- Card-Rows als Default-Message-Render: Sender-Header in Channel-Farbe, Body neue Zeile, dezenter Trenner.
|
||||
`Compact Density`-Toggle in Aussehen schaltet zurück auf den Einzeiler
|
||||
- Bug-Fix: Settings speichern löscht den Chat-Verlauf nicht mehr. Refilter läuft jetzt nur wenn Filter-relevante
|
||||
Settings geändert wurden — Cosmetic-Änderungen lassen den Chat unverändert. Persistente und Auto-Tell-Tabs überleben
|
||||
beide
|
||||
(envelope/star/heart/bell/bookmark/flag/fire) plus eigene Farbe aus 12-Farb-Palette — 84
|
||||
Kombinationen, gleicher Partner ergibt konsistent dieselbe
|
||||
- Pulsierender roter Dot oben rechts am Sidebar-Icon zeigt ungelesene Nachrichten an. Sanft,
|
||||
2-Sekunden-Cycle, deaktivierbar über `Configuration.ReduceMotion` (UI-Toggle in v1.3.0)
|
||||
- Bottom-Status-Bar (22 px) mit fünf Live-Slots: aktiver Channel + Color-Dot, Privacy-Badge,
|
||||
Tab/Message-Counter, Auto-Tell-Counter, Plugin-Version. Update 1×/Sek
|
||||
- Card-Rows als Default-Message-Render: Sender-Header in Channel-Farbe, Body neue Zeile, dezenter
|
||||
Trenner. `Compact Density`-Toggle in Aussehen schaltet zurück auf den Einzeiler
|
||||
- Bug-Fix: Settings speichern löscht den Chat-Verlauf nicht mehr. Refilter läuft jetzt nur wenn
|
||||
Filter-relevante Settings geändert wurden — Cosmetic-Änderungen lassen den Chat unverändert.
|
||||
Persistente und Auto-Tell-Tabs überleben beide
|
||||
- Bug-Fix: Hellion-Schrift (Exo 2) blockt die Schriftgröße nicht mehr — 4K-User können hochskalieren
|
||||
- Migration v14 → v15: alte Theme-Felder entfernt, alle anderen Settings bleiben
|
||||
|
||||
|
||||
@@ -3,27 +3,31 @@ subtitle: "Settings Cleanup"
|
||||
versionsnatur: "UX-Polish-Cycle"
|
||||
---
|
||||
|
||||
- Settings-Übersicht thematisch re-sortiert: zusammenhängende Optionen wohnen jetzt zusammen, jede Card hat einen kurzen
|
||||
Untertitel — kein Raten mehr wo eine Setting steckt
|
||||
- Drei neue Cards: **Theme & Layout** (Theme-Picker, Fenster-Style, Zeitstempel-Style), **Schriften & Farben**
|
||||
(Schriftart, Schriftgröße, Chat-Farben pro Channel), **Daten-Verwaltung** (Aufbewahrung, Cleanup, Export, DB-Viewer,
|
||||
Advanced-Tools — vorher zwischen Datenschutz und Datenbank verteilt)
|
||||
- Settings-Übersicht thematisch re-sortiert: zusammenhängende Optionen wohnen jetzt zusammen, jede
|
||||
Card hat einen kurzen Untertitel — kein Raten mehr wo eine Setting steckt
|
||||
- Drei neue Cards: **Theme & Layout** (Theme-Picker, Fenster-Style, Zeitstempel-Style), **Schriften
|
||||
& Farben** (Schriftart, Schriftgröße, Chat-Farben pro Channel), **Daten-Verwaltung**
|
||||
(Aufbewahrung, Cleanup, Export, DB-Viewer, Advanced-Tools — vorher zwischen Datenschutz und
|
||||
Datenbank verteilt)
|
||||
- Datenschutz fokussiert sich jetzt auf eine Aufgabe: den Privacy-Filter
|
||||
- Der Auto-Tell-Tabs-History-Preload-Slider ist von Datenschutz nach Chat → Auto-Tell-Tabs umgezogen
|
||||
- KeybindMode wohnt jetzt unter Allgemein → Eingabe statt unter Sprache
|
||||
- Vier tote Schema-Felder entfernt (alle obsolet seit der Theme-Engine in v1.1.0): `Stilüberschreiben`-Toggle,
|
||||
`Stilname`-Auswahl, alter `WindowAlpha`-Slider, ungenutztes `ShowThemeQuickPicker`
|
||||
- Vier tote Schema-Felder entfernt (alle obsolet seit der Theme-Engine in v1.1.0):
|
||||
`Stilüberschreiben`-Toggle, `Stilname`-Auswahl, alter `WindowAlpha`-Slider, ungenutztes
|
||||
`ShowThemeQuickPicker`
|
||||
- Migration v15 → v16: alter `WindowAlpha`-Wert wird automatisch nach
|
||||
`Theme & Layout → Fenster-Style → Fenster-Transparenz` gemappt (nur wenn der Slider noch auf Default 0.85 stand, sonst
|
||||
gewinnt der User-Wert). Backup der Pre-v16-Config liegt unter `pluginConfigs/HellionChat.json.pre-v16-backup`. User
|
||||
die `Stilüberschreiben` aktiv hatten sehen einen einmaligen Hinweis-Toast
|
||||
- UX-Default-Bumps für Bestand-User mit Default-Werten: Card-Rows-Layout zurück auf Single-Line, NG+ standardmäßig
|
||||
hidden, gleiche Zeitstempel werden zusammengefasst, MaxLinesToRender auf konservativere 2500
|
||||
- Frische Installs starten mit dem Hellion-Brand-Chat-Color-Preset out-of-the-box (der First-Run-Wizard hat keine
|
||||
Preset-Wahl)
|
||||
- Hinweis zum Window-Transparenz-Slider in der Beschreibung: Dalamud's per-Window-Hamburger-Menü (oben rechts in der
|
||||
Titelleiste) bietet eigene Overrides für Deckkraft, Hintergrund-Blur, Anpinnen und Durchklick — die haben Vorrang über
|
||||
unseren Slider für das jeweilige Fenster
|
||||
`Theme & Layout → Fenster-Style → Fenster-Transparenz` gemappt (nur wenn der Slider noch auf
|
||||
Default 0.85 stand, sonst gewinnt der User-Wert). Backup der Pre-v16-Config liegt unter
|
||||
`pluginConfigs/HellionChat.json.pre-v16-backup`. User die `Stilüberschreiben` aktiv hatten sehen
|
||||
einen einmaligen Hinweis-Toast
|
||||
- UX-Default-Bumps für Bestand-User mit Default-Werten: Card-Rows-Layout zurück auf Single-Line, NG+
|
||||
standardmäßig hidden, gleiche Zeitstempel werden zusammengefasst, MaxLinesToRender auf
|
||||
konservativere 2500
|
||||
- Frische Installs starten mit dem Hellion-Brand-Chat-Color-Preset out-of-the-box (der
|
||||
First-Run-Wizard hat keine Preset-Wahl)
|
||||
- Hinweis zum Window-Transparenz-Slider in der Beschreibung: Dalamud's per-Window-Hamburger-Menü
|
||||
(oben rechts in der Titelleiste) bietet eigene Overrides für Deckkraft, Hintergrund-Blur, Anpinnen
|
||||
und Durchklick — die haben Vorrang über unseren Slider für das jeweilige Fenster
|
||||
|
||||
Pure UX-Polish, keine neuen Features. Nächster Cycle (v1.3.0): Animation-Polish (Lerps, Theme-Crossfade, Quick-Picker)
|
||||
wie ursprünglich geplant.
|
||||
Pure UX-Polish, keine neuen Features. Nächster Cycle (v1.3.0): Animation-Polish (Lerps,
|
||||
Theme-Crossfade, Quick-Picker) wie ursprünglich geplant.
|
||||
|
||||
@@ -3,21 +3,23 @@ subtitle: "Theme Expansion"
|
||||
versionsnatur: "Theme-Pack-Patch"
|
||||
---
|
||||
|
||||
- Vier neue Built-in-Themes verlängern die Auswahl im Picker — keine Engine-Änderung, keine Settings angefasst, einfach
|
||||
mehr Farboptionen
|
||||
- **Night Blue** — Royal Blue auf tiefem Marineblau. Kühles Tech-Dashboard-Mood, bewusst neutral gehalten damit es sich
|
||||
nicht mit den Brand-Themes beißt
|
||||
- **Indigo Violet** — Royal Violet auf Deep Indigo mit Türkis-Mint-Counter für Aurora-Glitter-Stimmung. Schwester von
|
||||
Event Horizon, aber dunkler und dichter; der Türkis-Akzent hält die beiden klar auseinander
|
||||
- **Forge Merchantman** — Patina-Bronze auf Workshop-Slate mit warmem Bernstein-Counter. Hellion Forge bekommt ein
|
||||
eigenes Theme im Plugin selbst — Schwester von Hellion Arctic, aber grüner und wärmer statt kaltem Cyan
|
||||
- Vier neue Built-in-Themes verlängern die Auswahl im Picker — keine Engine-Änderung, keine Settings
|
||||
angefasst, einfach mehr Farboptionen
|
||||
- **Night Blue** — Royal Blue auf tiefem Marineblau. Kühles Tech-Dashboard-Mood, bewusst neutral
|
||||
gehalten damit es sich nicht mit den Brand-Themes beißt
|
||||
- **Indigo Violet** — Royal Violet auf Deep Indigo mit Türkis-Mint-Counter für
|
||||
Aurora-Glitter-Stimmung. Schwester von Event Horizon, aber dunkler und dichter; der Türkis-Akzent
|
||||
hält die beiden klar auseinander
|
||||
- **Forge Merchantman** — Patina-Bronze auf Workshop-Slate mit warmem Bernstein-Counter. Hellion
|
||||
Forge bekommt ein eigenes Theme im Plugin selbst — Schwester von Hellion Arctic, aber grüner und
|
||||
wärmer statt kaltem Cyan
|
||||
- **Hellion Spectrum** — Farbenblind-sichere Channel-Farben (Deuteranopie/Protanopie) auf Basis der
|
||||
Wong/Okabe-Ito-Palette. Channel-Identität bleibt erhalten (Tell pink, Yell gelb, Shout orange, Party blau, FC grün);
|
||||
die Töne sind so gewählt dass jeder Channel auch unter Rot-Grün-Schwäche klar trennbar bleibt. Deckt rund 99 % aller
|
||||
CVD-Fälle ab
|
||||
- Kein Schema-Bump, keine Migration. Das Default-Theme bleibt **Hellion Arctic**, eigene Custom-Themes laufen
|
||||
unverändert weiter
|
||||
Wong/Okabe-Ito-Palette. Channel-Identität bleibt erhalten (Tell pink, Yell gelb, Shout orange,
|
||||
Party blau, FC grün); die Töne sind so gewählt dass jeder Channel auch unter Rot-Grün-Schwäche
|
||||
klar trennbar bleibt. Deckt rund 99 % aller CVD-Fälle ab
|
||||
- Kein Schema-Bump, keine Migration. Das Default-Theme bleibt **Hellion Arctic**, eigene
|
||||
Custom-Themes laufen unverändert weiter
|
||||
- Theme-Katalog wächst damit von fünf auf neun Built-ins
|
||||
|
||||
Reines Theme-Pack zwischen v1.2.1 und dem nächsten Polish-Cycle. Eine Tritan-Variante (Spectrum für Blau-Gelb-Schwäche)
|
||||
kann später nachgeliefert werden, falls Bedarf kommt.
|
||||
Reines Theme-Pack zwischen v1.2.1 und dem nächsten Polish-Cycle. Eine Tritan-Variante (Spectrum für
|
||||
Blau-Gelb-Schwäche) kann später nachgeliefert werden, falls Bedarf kommt.
|
||||
|
||||
@@ -4,15 +4,17 @@ versionsnatur: "Plugin-Integration-Cycle 1"
|
||||
---
|
||||
|
||||
- Erste Plugin-Integration eingebaut, Cycle 1 von 6 auf der Roadmap
|
||||
- **Honorific-Custom-Titles im Chat-Header** — der Titel den du in Honorific gesetzt hast erscheint jetzt links über dem
|
||||
Message-Log mit der von dir gewählten Farbe, Auto-Hide wenn Honorific nicht installiert ist oder kein Custom-Titel
|
||||
aktiv ist
|
||||
- **Krone-Icon plus Tooltip** vor dem Titel-Text, damit klar ist woher der Slot kommt ohne dass der User raten muss
|
||||
- **Neuer Integrations-Settings-Tab** mit Status-Indikator (erkannt, nicht installiert, inkompatibel) und Toggle. Plus
|
||||
Vorschau-Block der die fünf weiteren geplanten Cycles ankündigt: Kontextmenü-Aktionen, Smart Notifications
|
||||
(NotificationMaster), RP-Status-Block (Moodles und LightlessClient), ExtraChat-Channels, Quick-DM-Button
|
||||
(XIVInstantMessenger)
|
||||
- **Maintainer-Attribution** im Tab als Höflichkeits-Geste, zwei Buttons zum Honorific-Repo und zum Caraxi-Profil. Plus
|
||||
Hellion-Forge-Discord-Button für Community-Vorschläge zu künftigen Integrationen
|
||||
- Keine Migration, keine Schema-Änderung. Wer Honorific eh schon nutzt sieht den Custom-Titel automatisch sobald
|
||||
HellionChat aktualisiert
|
||||
- **Honorific-Custom-Titles im Chat-Header** — der Titel den du in Honorific gesetzt hast erscheint
|
||||
jetzt links über dem Message-Log mit der von dir gewählten Farbe, Auto-Hide wenn Honorific nicht
|
||||
installiert ist oder kein Custom-Titel aktiv ist
|
||||
- **Krone-Icon plus Tooltip** vor dem Titel-Text, damit klar ist woher der Slot kommt ohne dass der
|
||||
User raten muss
|
||||
- **Neuer Integrations-Settings-Tab** mit Status-Indikator (erkannt, nicht installiert,
|
||||
inkompatibel) und Toggle. Plus Vorschau-Block der die fünf weiteren geplanten Cycles ankündigt:
|
||||
Kontextmenü-Aktionen, Smart Notifications (NotificationMaster), RP-Status-Block (Moodles und
|
||||
LightlessClient), ExtraChat-Channels, Quick-DM-Button (XIVInstantMessenger)
|
||||
- **Maintainer-Attribution** im Tab als Höflichkeits-Geste, zwei Buttons zum Honorific-Repo und zum
|
||||
Caraxi-Profil. Plus Hellion-Forge-Discord-Button für Community-Vorschläge zu künftigen
|
||||
Integrationen
|
||||
- Keine Migration, keine Schema-Änderung. Wer Honorific eh schon nutzt sieht den Custom-Titel
|
||||
automatisch sobald HellionChat aktualisiert
|
||||
|
||||
@@ -5,19 +5,20 @@ versionsnatur: Stability-Hotfix
|
||||
|
||||
**Hellion Chat 1.4.0 — Critical Lifecycle Fixes**
|
||||
|
||||
Erster Sub-Patch der v1.4.x Polish-Sweep-Serie. Sieben bekannte Lifecycle- und Race-Bugs aus den Audit-Pässen
|
||||
abgearbeitet, bevor Performance- und Architektur-Refactors draufkommen.
|
||||
Erster Sub-Patch der v1.4.x Polish-Sweep-Serie. Sieben bekannte Lifecycle- und Race-Bugs aus den
|
||||
Audit-Pässen abgearbeitet, bevor Performance- und Architektur-Refactors draufkommen.
|
||||
|
||||
- **SQLite-Dispose** lehnt sich nicht mehr an GC-Druck zur Datei-Freigabe an, Pooling=false auf der Connection macht den
|
||||
manuellen GC.Collect überflüssig
|
||||
- **Worker-Threads** (PendingMessage, RetentionSweep) sind jetzt explizit IsBackground=true, das Plugin-Domain kann
|
||||
sauber unloaden bei XIVLauncher-Reload ohne darauf zu warten
|
||||
- **EmoteCache-Loader** von async-void auf async-Task mit shared Task-Tracker, drain-on-Dispose. Kein Schreib-Risiko
|
||||
mehr auf disposed EmoteImages-Einträge nach Plugin-Reload
|
||||
- **SQLite-Dispose** lehnt sich nicht mehr an GC-Druck zur Datei-Freigabe an, Pooling=false auf der
|
||||
Connection macht den manuellen GC.Collect überflüssig
|
||||
- **Worker-Threads** (PendingMessage, RetentionSweep) sind jetzt explizit IsBackground=true, das
|
||||
Plugin-Domain kann sauber unloaden bei XIVLauncher-Reload ohne darauf zu warten
|
||||
- **EmoteCache-Loader** von async-void auf async-Task mit shared Task-Tracker, drain-on-Dispose.
|
||||
Kein Schreib-Risiko mehr auf disposed EmoteImages-Einträge nach Plugin-Reload
|
||||
- **DisposeAsync-Timeout** (10s) warnt jetzt laut statt silent zu failen
|
||||
- **Plugin-Dispose** flushed pending DeferredSave bevor Services abgebaut werden, Settings-Änderungen aus den letzten
|
||||
Frames vor Disable überleben jetzt zuverlässig
|
||||
- **v13→v14 Config-Migration** liest pre-v13-Backup und überträgt HellionThemeWindowOpacity in das neue
|
||||
WindowOpacity-Feld statt auf 0.85 zurückzufallen
|
||||
- **Plugin-Dispose** flushed pending DeferredSave bevor Services abgebaut werden,
|
||||
Settings-Änderungen aus den letzten Frames vor Disable überleben jetzt zuverlässig
|
||||
- **v13→v14 Config-Migration** liest pre-v13-Backup und überträgt HellionThemeWindowOpacity in das
|
||||
neue WindowOpacity-Feld statt auf 0.85 zurückzufallen
|
||||
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions-Änderungen außer dass Reload und Shutdown spürbar sauberer laufen.
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions-Änderungen außer dass Reload und Shutdown
|
||||
spürbar sauberer laufen.
|
||||
|
||||
@@ -5,23 +5,25 @@ versionsnatur: Performance-Patch
|
||||
|
||||
**Hellion Chat 1.4.1 — Theme Engine Performance**
|
||||
|
||||
Zweiter Sub-Patch der v1.4.x Polish-Sweep-Serie. Heap-Pressure aus dem Theme-Engine-Render-Pfad eliminiert,
|
||||
Custom-Theme- Hot-Reload überlebt transiente File-Locks beim Editor-Save. Plus zehnter Built-In und überarbeitete
|
||||
Author-Credits.
|
||||
Zweiter Sub-Patch der v1.4.x Polish-Sweep-Serie. Heap-Pressure aus dem Theme-Engine-Render-Pfad
|
||||
eliminiert, Custom-Theme- Hot-Reload überlebt transiente File-Locks beim Editor-Save. Plus zehnter
|
||||
Built-In und überarbeitete Author-Credits.
|
||||
|
||||
- **ABGR-Cache auf den Theme-Records.** Beim Theme-Register (Built-In oder Custom) werden alle Color-Slots einmalig in
|
||||
ABGR-Pack-Form vor-konvertiert. HellionStyle.PushGlobal liest aus dem Cache statt pro Slot pro Frame durch
|
||||
ColourUtil.RgbaToAbgr zu jagen. Real gemessene Frame-Time-Recovery: **~13 %** in typischer Render-Szene
|
||||
(Plan-Erwartung war 2-6 % konservativ, real ~10-15 %)
|
||||
- **Custom-Theme File-Lock-Härtung.** Wenn der User ein Theme-JSON gerade speichert während HellionChat reloaden will,
|
||||
fängt der Loader jetzt explizit Sharing-Violation und Lock-Violation ab. Last-Known-Good-Snapshot bleibt im Picker,
|
||||
beim nächsten Tick wird automatisch retry'd — vorher fiel das Theme aus der Liste bis zum Plugin-Reload
|
||||
- **Defensive Cache-Refresh beim Theme-Switch.** Falls ein Theme auf einem alten Pfad ohne Cache-Fill in den Speicher
|
||||
gekommen ist, holt Switch() das beim Anwenden nach
|
||||
- **Synthwave Sunset als zehnter Built-In.** Hot Magenta + Cyan auf Mitternachts-Violett, 80s-Neon-Grid-Vibes für
|
||||
Late-Night-Raids
|
||||
- **Author-Credits konsolidiert.** Brand-Themes laufen jetzt unter „Hellion Forge". Mint Grove und Forge Merchantman
|
||||
werden Carla Beleandis als Community-Geste zugeschrieben.
|
||||
- **ABGR-Cache auf den Theme-Records.** Beim Theme-Register (Built-In oder Custom) werden alle
|
||||
Color-Slots einmalig in ABGR-Pack-Form vor-konvertiert. HellionStyle.PushGlobal liest aus dem
|
||||
Cache statt pro Slot pro Frame durch ColourUtil.RgbaToAbgr zu jagen. Real gemessene
|
||||
Frame-Time-Recovery: **~13 %** in typischer Render-Szene (Plan-Erwartung war 2-6 % konservativ,
|
||||
real ~10-15 %)
|
||||
- **Custom-Theme File-Lock-Härtung.** Wenn der User ein Theme-JSON gerade speichert während
|
||||
HellionChat reloaden will, fängt der Loader jetzt explizit Sharing-Violation und Lock-Violation
|
||||
ab. Last-Known-Good-Snapshot bleibt im Picker, beim nächsten Tick wird automatisch retry'd —
|
||||
vorher fiel das Theme aus der Liste bis zum Plugin-Reload
|
||||
- **Defensive Cache-Refresh beim Theme-Switch.** Falls ein Theme auf einem alten Pfad ohne
|
||||
Cache-Fill in den Speicher gekommen ist, holt Switch() das beim Anwenden nach
|
||||
- **Synthwave Sunset als zehnter Built-In.** Hot Magenta + Cyan auf Mitternachts-Violett,
|
||||
80s-Neon-Grid-Vibes für Late-Night-Raids
|
||||
- **Author-Credits konsolidiert.** Brand-Themes laufen jetzt unter „Hellion Forge". Mint Grove und
|
||||
Forge Merchantman werden Carla Beleandis als Community-Geste zugeschrieben.
|
||||
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames in Theme-getrieben rendernden
|
||||
Szenen merklich glatter laufen und ein neues Theme im Picker steht.
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames in
|
||||
Theme-getrieben rendernden Szenen merklich glatter laufen und ein neues Theme im Picker steht.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
subtitle: Symbol-Picker und Tell-History Fix
|
||||
versionsnatur: Feature-Patch + Hotfix
|
||||
---
|
||||
|
||||
- Symbol-Picker im Chat-Eingang: ein kleiner Smile-Button links neben dem Kanal-Indikator öffnet ein
|
||||
Popup mit zwei Tabs. Der erste listet alle 161 FFXIV-PUA-Glyphen (Dalamuds SeIconChar); der zweite
|
||||
trägt 97 verifizierte BMP-Symbole (Latin-Marken, Währungen, das ganze griechische Alphabet,
|
||||
Geometrie, Spielkarten, Noten) — jedes davon über `/echo` und `/say` in einer vierrundigen
|
||||
Whitelist-Probe durchgereicht, damit der Channel-Render dem entspricht, was der Picker anzeigt.
|
||||
Klick fügt das Symbol an der Cursor-Position ein, Multi-Insert lässt das Popup offen, eine
|
||||
Recent-Used-Leiste zeigt die letzten sechzehn Picks über beide Tabs. Toggle in Settings → Chat →
|
||||
Nachrichten-Verhalten, Default an.
|
||||
- Verlauf in angepinnten Tell-Tabs lädt wieder vollständig: ein versteckter 500-Zeilen-Scan-Cap in
|
||||
PreloadHistory hat das User-Setting `AutoTellTabsHistoryPreload` überschrieben, wodurch
|
||||
weniger-frequente Tell-Partner ihren Backlog verloren haben sobald die Scan-Schicht mit anderen
|
||||
Chat-Partnern voll lief. Cap ist raus, der Index auf `(Receiver, Date)` hält die Query schnell.
|
||||
- Slash-Command-Teardown: /hellion, /hellionView, /hellionDebugger (und im Debug-Build
|
||||
/hellionSeString) sind als private Felder gecached. Plugin-Dispose detached die echte
|
||||
Registrierung, statt mit identischen Args neu zu registrieren — schließt eine latente
|
||||
Wartungs-Falle aus v1.4.9.
|
||||
- v1.4.x-Polish-Sweep endet hier. Der ImGuiListClipper-Refactor von der v1.4.10-Reserve-Liste wurde
|
||||
gecancelt, nachdem der Cross- Plattform-Smoke gezeigt hat dass das Scroll-Gummi ein Wine/Linux-
|
||||
Quirk ist — Windows-User haben es nie gesehen. Spike dafür kommt in einem späteren Patch. Nächster
|
||||
Major-Cycle ist v1.5.0 mit der DI-Container-Adoption (`Microsoft.Extensions.Hosting` +
|
||||
`ILogger<T>`) nach dem Lightless-Vorbild.
|
||||
- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-Aufwand.
|
||||
@@ -5,25 +5,27 @@ versionsnatur: Performance-Patch
|
||||
|
||||
**Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path**
|
||||
|
||||
Dritter Sub-Patch der v1.4.x Polish-Sweep-Serie. Drei Per-Frame-Allokations-Quellen aus dem ChatLogWindow-Render- Pfad
|
||||
und der Settings-StatusBar eliminiert.
|
||||
Dritter Sub-Patch der v1.4.x Polish-Sweep-Serie. Drei Per-Frame-Allokations-Quellen aus dem
|
||||
ChatLogWindow-Render- Pfad und der Settings-StatusBar eliminiert.
|
||||
|
||||
- **Card-Mode-Border-Loop entlastet.** DrawMessages hebt Theme, DrawList, Window-Left, Window-Right und die ABGR-
|
||||
Border-Color einmalig vor den Per-Message-Loop. Bei 100 sichtbaren Messages sind das gut 500 redundante P/Invokes und
|
||||
Property-Reads, die der Hoist eliminiert. Pop-Out- Heavy-Setups (mehrere parallele Chat-Windows) profitieren
|
||||
proportional, weil der Hoist pro DrawMessages-Call greift, also pro Window
|
||||
- **Auto-Tell Tab-Tint und Icon gecached.** Die Hash-Color- Berechnung für Auto-Tell-Tabs lief pro Tab pro Frame, mit
|
||||
zwei String-Allokationen pro Tab (eine für Tint-Hash, eine für Icon-Hash). Der neue TabTintCache liest pre-computed
|
||||
Werte aus dem Tab und rechnet nur neu wenn das Tell-Target drifted. Beide Caches haben separate Validation-Keys, also
|
||||
keine Cross-Invalidation zwischen Tint- und Icon-Pfad. AutoTellTabTint selbst bleibt pure Hash-Helper, weiterhin ohne
|
||||
Tab-Awareness
|
||||
- **StatusBar-Aggregation hinter Cache-Gate.** Die Status- Leiste am unteren Window-Rand summiert die Tab-Message-
|
||||
Counts und zählt die Auto-Tell-Tabs pro Frame. Der Cache- Gate (1 Sekunde) lag bisher hinter den LINQ-Pfaden, also
|
||||
liefen Sum und Count trotzdem pro Frame. Jetzt vor dem Gate, plus die LINQ-Pfade durch eine Single-Pass-Foreach
|
||||
ersetzt. Die Aggregation läuft auf etwa 1 % der Frames
|
||||
- **Card-Mode-Border-Loop entlastet.** DrawMessages hebt Theme, DrawList, Window-Left, Window-Right
|
||||
und die ABGR- Border-Color einmalig vor den Per-Message-Loop. Bei 100 sichtbaren Messages sind das
|
||||
gut 500 redundante P/Invokes und Property-Reads, die der Hoist eliminiert. Pop-Out- Heavy-Setups
|
||||
(mehrere parallele Chat-Windows) profitieren proportional, weil der Hoist pro DrawMessages-Call
|
||||
greift, also pro Window
|
||||
- **Auto-Tell Tab-Tint und Icon gecached.** Die Hash-Color- Berechnung für Auto-Tell-Tabs lief pro
|
||||
Tab pro Frame, mit zwei String-Allokationen pro Tab (eine für Tint-Hash, eine für Icon-Hash). Der
|
||||
neue TabTintCache liest pre-computed Werte aus dem Tab und rechnet nur neu wenn das Tell-Target
|
||||
drifted. Beide Caches haben separate Validation-Keys, also keine Cross-Invalidation zwischen Tint-
|
||||
und Icon-Pfad. AutoTellTabTint selbst bleibt pure Hash-Helper, weiterhin ohne Tab-Awareness
|
||||
- **StatusBar-Aggregation hinter Cache-Gate.** Die Status- Leiste am unteren Window-Rand summiert
|
||||
die Tab-Message- Counts und zählt die Auto-Tell-Tabs pro Frame. Der Cache- Gate (1 Sekunde) lag
|
||||
bisher hinter den LINQ-Pfaden, also liefen Sum und Count trotzdem pro Frame. Jetzt vor dem Gate,
|
||||
plus die LINQ-Pfade durch eine Single-Pass-Foreach ersetzt. Die Aggregation läuft auf etwa 1 % der
|
||||
Frames
|
||||
|
||||
Realistische Frame-Time-Recovery: 2-5 % in typischen Szenen, Pop-Out-Heavy-Setups potenziell mehr durch die Card-Border-
|
||||
Multiplikation pro Window.
|
||||
Realistische Frame-Time-Recovery: 2-5 % in typischen Szenen, Pop-Out-Heavy-Setups potenziell mehr
|
||||
durch die Card-Border- Multiplikation pro Window.
|
||||
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames im Chat-Log und in der
|
||||
Settings-Statusleiste merklich glatter laufen.
|
||||
Keine Schema-Bumps, keine User-sichtbaren Funktions- Änderungen außer dass die Frames im Chat-Log
|
||||
und in der Settings-Statusleiste merklich glatter laufen.
|
||||
|
||||
@@ -5,25 +5,29 @@ versionsnatur: Architecture-Refactor
|
||||
|
||||
**Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover**
|
||||
|
||||
Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin- Lifecycle auf Dalamud's `IAsyncDalamudPlugin`-API migriert und
|
||||
das Custom-Repo zieht von GitHub auf Gitea um.
|
||||
Vierter Sub-Patch der v1.4.x Polish-Sweep-Serie. Plugin- Lifecycle auf Dalamud's
|
||||
`IAsyncDalamudPlugin`-API migriert und das Custom-Repo zieht von GitHub auf Gitea um.
|
||||
|
||||
- **Async-Plugin-Architektur.** Konstruktor übernimmt nur noch die Bootstrap-Essentials (Config-Load, Language-Init,
|
||||
Conflict-Detection). Migrationen, Service-Allokationen, Window-Konstruktion und Hook-Subscription wandern in
|
||||
LoadAsync, sodass Dalamud die UI während der schweren Arbeit responsive halten kann. Per-Line-CaptureFailure in
|
||||
DisposeAsync mirrort LightlessSync's Pattern, plus Idempotency-Guard gegen Reload-Races
|
||||
- **Custom-Repo-URL umgezogen auf Gitea.** Bestehende Tester müssen einmalig in XIVLauncher die Custom-Repo-URL auf
|
||||
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json` umstellen, dann
|
||||
XIVLauncher neu starten. Das alte GitHub-Repo bleibt als eingefrorener v1.4.2-Snapshot stehen und wird nicht mehr
|
||||
aktualisiert
|
||||
- **Schema-Gate statt Migrations-Kette.** Die v9 → v16 Migrationen sind raus, ersetzt durch einen harten Schema-Check in
|
||||
Phase 1. Configs auf Schema v16+ laden direkt; ältere Configs (vor v1.2.1) bekommen jetzt eine klare „install v1.4.2
|
||||
first"-Fehlermeldung statt eines impliziten Migrations-Pfads
|
||||
- **AutoTranslate-Cache läuft im Hintergrund.** Der Cache füllt sich jetzt fire-and-forget statt blockierend im
|
||||
Plugin-Load. Trade-off: die erste Auto-Translate-Nutzung einer Session kann einen kurzen Hitch haben, dafür kein
|
||||
300-ms-Block beim Plugin-Start
|
||||
- **Plugin-Load-Zeit ehrlich.** Median 3,7 s über fünf Reloads, vergleichbar mit v1.4.2. Der Async-Refactor ist
|
||||
Foundation für künftige Lazy-Init-Optimierungen (v1.4.4) und Code-Architektur-Hygiene, kein direkter User-spürbarer
|
||||
Speed-Win in dieser Release
|
||||
- **Async-Plugin-Architektur.** Konstruktor übernimmt nur noch die Bootstrap-Essentials
|
||||
(Config-Load, Language-Init, Conflict-Detection). Migrationen, Service-Allokationen,
|
||||
Window-Konstruktion und Hook-Subscription wandern in LoadAsync, sodass Dalamud die UI während der
|
||||
schweren Arbeit responsive halten kann. Per-Line-CaptureFailure in DisposeAsync mirrort
|
||||
LightlessSync's Pattern, plus Idempotency-Guard gegen Reload-Races
|
||||
- **Custom-Repo-URL umgezogen auf Gitea.** Bestehende Tester müssen einmalig in XIVLauncher die
|
||||
Custom-Repo-URL auf
|
||||
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||
umstellen, dann XIVLauncher neu starten. Das alte GitHub-Repo bleibt als eingefrorener
|
||||
v1.4.2-Snapshot stehen und wird nicht mehr aktualisiert
|
||||
- **Schema-Gate statt Migrations-Kette.** Die v9 → v16 Migrationen sind raus, ersetzt durch einen
|
||||
harten Schema-Check in Phase 1. Configs auf Schema v16+ laden direkt; ältere Configs (vor v1.2.1)
|
||||
bekommen jetzt eine klare „install v1.4.2 first"-Fehlermeldung statt eines impliziten
|
||||
Migrations-Pfads
|
||||
- **AutoTranslate-Cache läuft im Hintergrund.** Der Cache füllt sich jetzt fire-and-forget statt
|
||||
blockierend im Plugin-Load. Trade-off: die erste Auto-Translate-Nutzung einer Session kann einen
|
||||
kurzen Hitch haben, dafür kein 300-ms-Block beim Plugin-Start
|
||||
- **Plugin-Load-Zeit ehrlich.** Median 3,7 s über fünf Reloads, vergleichbar mit v1.4.2. Der
|
||||
Async-Refactor ist Foundation für künftige Lazy-Init-Optimierungen (v1.4.4) und
|
||||
Code-Architektur-Hygiene, kein direkter User-spürbarer Speed-Win in dieser Release
|
||||
|
||||
Keine User-sichtbaren Funktions-Änderungen außer dem Repo-URL-Update. Settings, Themes und Tabs bleiben unangetastet.
|
||||
Keine User-sichtbaren Funktions-Änderungen außer dem Repo-URL-Update. Settings, Themes und Tabs
|
||||
bleiben unangetastet.
|
||||
|
||||
@@ -5,30 +5,33 @@ versionsnatur: Wartung und Robustheit
|
||||
|
||||
**Hellion Chat 1.4.4 — Threading- und IPC-Sicherheits-Politur**
|
||||
|
||||
Fünfter Sub-Patch der v1.4.x Polish-Sweep-Serie. Threading-Annahmen werden explizit pro Methode dokumentiert, ein
|
||||
Hot-Path-Lock im Auto-Tell-Tab-Counter fällt weg, IPC-Cleanup wird sichtbar wenn er fehlschlägt und der Privacy-Filter
|
||||
spricht jetzt bei unbekannten ChatTypes.
|
||||
Fünfter Sub-Patch der v1.4.x Polish-Sweep-Serie. Threading-Annahmen werden explizit pro Methode
|
||||
dokumentiert, ein Hot-Path-Lock im Auto-Tell-Tab-Counter fällt weg, IPC-Cleanup wird sichtbar wenn
|
||||
er fehlschlägt und der Privacy-Filter spricht jetzt bei unbekannten ChatTypes.
|
||||
|
||||
- **AutoTellTabsService Hot-Path-Lock entfernt.** `ActiveTempTabCount` hat bisher pro Render-Frame ein LINQ-Count unter
|
||||
einem Lock gemacht. Jetzt läuft das über einen Interlocked-Counter der parallel zur Tabs-Liste mitgeführt wird,
|
||||
inklusive Resync-Hook für den Snapshot-Restore-Pfad in `SaveConfig`. Plus Pure-Helper-Test-Mirror in der Build-Suite
|
||||
damit die Atomicity-Semantik nicht versehentlich wegrefactored wird
|
||||
- **HonorificService selbst-dokumentierende Threading-Banner.** Statt eines Block-Comments am Klassen-Ende hat jede
|
||||
IPC-Callback-Methode jetzt einen 1-Zeilen-Banner darüber, der den Thread-Kontext direkt am Call-Site benennt
|
||||
(framework only, framework scheduled, any). Mehr Hilfe für künftige Reviews als ein abstraktes Threading-Kapitel
|
||||
- **Unsubscribe-Failure ist jetzt sichtbar.** `TryUnsubscribe` hat ein Honorific-Unsubscribe-Failure bisher als Debug
|
||||
geloggt, was bei Standard-Loglevel verschluckt wurde. Eine geleakte Subscription kann den Service über Plugin-Reloads
|
||||
hinweg leben lassen, also läuft der Log jetzt auf Warning
|
||||
- **AutoTranslate-Warmup blockiert den Plugin-Unload nicht mehr.** Der Cache-Warmup-Thread war ohne `IsBackground=true`
|
||||
unterwegs, was den Unload um 100-300 ms verzögern konnte. Pattern-Match zu MessageManager und RetentionSweep (beide
|
||||
seit v1.4.0)
|
||||
- **Privacy-Filter loggt unbekannte ChatTypes.** Wenn FFXIV durch einen Patch einen neuen ChatType einführt der weder in
|
||||
der Whitelist noch in den Defaults steht, wird er bisher silent durch den Failsafe geleitet. Jetzt loggt der Filter
|
||||
einmalig pro Runtime eine Warning mit dem Type und dem Failsafe-Wert. Dedup über ein NonSerialized-HashSet, also kein
|
||||
Log-Spam
|
||||
- **Default-Flip für neue Installationen.** `PrivacyPersistUnknownChannels` startet bei neuen Configs jetzt auf `true`,
|
||||
damit ein Patch-bedingt neuer ChatType nicht stillschweigend gedroppt wird bevor der User entscheiden kann. Bestehende
|
||||
Configs behalten ihre Wahl, weil der Deserializer den Initializer überschreibt. Keine Migration, kein Schema-Bump
|
||||
- **AutoTellTabsService Hot-Path-Lock entfernt.** `ActiveTempTabCount` hat bisher pro Render-Frame
|
||||
ein LINQ-Count unter einem Lock gemacht. Jetzt läuft das über einen Interlocked-Counter der
|
||||
parallel zur Tabs-Liste mitgeführt wird, inklusive Resync-Hook für den Snapshot-Restore-Pfad in
|
||||
`SaveConfig`. Plus Pure-Helper-Test-Mirror in der Build-Suite damit die Atomicity-Semantik nicht
|
||||
versehentlich wegrefactored wird
|
||||
- **HonorificService selbst-dokumentierende Threading-Banner.** Statt eines Block-Comments am
|
||||
Klassen-Ende hat jede IPC-Callback-Methode jetzt einen 1-Zeilen-Banner darüber, der den
|
||||
Thread-Kontext direkt am Call-Site benennt (framework only, framework scheduled, any). Mehr Hilfe
|
||||
für künftige Reviews als ein abstraktes Threading-Kapitel
|
||||
- **Unsubscribe-Failure ist jetzt sichtbar.** `TryUnsubscribe` hat ein Honorific-Unsubscribe-Failure
|
||||
bisher als Debug geloggt, was bei Standard-Loglevel verschluckt wurde. Eine geleakte Subscription
|
||||
kann den Service über Plugin-Reloads hinweg leben lassen, also läuft der Log jetzt auf Warning
|
||||
- **AutoTranslate-Warmup blockiert den Plugin-Unload nicht mehr.** Der Cache-Warmup-Thread war ohne
|
||||
`IsBackground=true` unterwegs, was den Unload um 100-300 ms verzögern konnte. Pattern-Match zu
|
||||
MessageManager und RetentionSweep (beide seit v1.4.0)
|
||||
- **Privacy-Filter loggt unbekannte ChatTypes.** Wenn FFXIV durch einen Patch einen neuen ChatType
|
||||
einführt der weder in der Whitelist noch in den Defaults steht, wird er bisher silent durch den
|
||||
Failsafe geleitet. Jetzt loggt der Filter einmalig pro Runtime eine Warning mit dem Type und dem
|
||||
Failsafe-Wert. Dedup über ein NonSerialized-HashSet, also kein Log-Spam
|
||||
- **Default-Flip für neue Installationen.** `PrivacyPersistUnknownChannels` startet bei neuen
|
||||
Configs jetzt auf `true`, damit ein Patch-bedingt neuer ChatType nicht stillschweigend gedroppt
|
||||
wird bevor der User entscheiden kann. Bestehende Configs behalten ihre Wahl, weil der Deserializer
|
||||
den Initializer überschreibt. Keine Migration, kein Schema-Bump
|
||||
|
||||
Keine User-sichtbaren Funktions-Änderungen außer dem Default-Flip für neue Installationen. Settings, Themes, Tabs und
|
||||
das Privacy-Verhalten für Bestand bleiben unangetastet.
|
||||
Keine User-sichtbaren Funktions-Änderungen außer dem Default-Flip für neue Installationen. Settings,
|
||||
Themes, Tabs und das Privacy-Verhalten für Bestand bleiben unangetastet.
|
||||
|
||||
@@ -5,24 +5,27 @@ versionsnatur: UX-Polish-Cycle
|
||||
|
||||
**Hellion Chat 1.4.5 — UX und Robustheit**
|
||||
|
||||
Sechster Sub-Patch der v1.4.x Polish-Sweep-Serie. Render-Fehler im Chat-Fenster werden jetzt sichtbar, der
|
||||
First-Run-Wizard hat eine explizite Cancel-Schaltfläche, der Eingabe-Verlauf bleibt nicht mehr über Plugin-Reloads
|
||||
hinweg liegen, und die Statusleiste klippt in schmalen Fenstern nicht mehr.
|
||||
Sechster Sub-Patch der v1.4.x Polish-Sweep-Serie. Render-Fehler im Chat-Fenster werden jetzt
|
||||
sichtbar, der First-Run-Wizard hat eine explizite Cancel-Schaltfläche, der Eingabe-Verlauf bleibt
|
||||
nicht mehr über Plugin-Reloads hinweg liegen, und die Statusleiste klippt in schmalen Fenstern nicht
|
||||
mehr.
|
||||
|
||||
- **Fehler-Benachrichtigung im Chat-Fenster.** Wenn ein Render-Fehler in `DrawChatLog` auftritt, zeigt das Plugin jetzt
|
||||
eine einmalige Warning-Notification mit Verweis aufs `/xllog`, statt das Fenster stillschweigend leer zu lassen. Der
|
||||
Stack-Trace selbst geht weiter via `Plugin.Log.Error` ins Logfile. De-Dup über Per-Session-Bool, damit ein
|
||||
wiederkehrender Fehler die Notification-Stack nicht pro Frame neu vollkippt
|
||||
- **First-Run-Wizard trennt Accept und Close.** `OnClose` setzt nicht mehr stillschweigend `FirstRunCompleted=true`,
|
||||
also lässt das X den Wizard schwebend zurück und er kommt beim nächsten Plugin-Reload wieder. Eine neue „Später —
|
||||
Defaults behalten"-Schaltfläche im Footer ist der explizite Weg, ohne Profil-Auswahl rauszukommen. Strings bilingual
|
||||
EN+DE plus Tooltip
|
||||
- **Eingabe-Verlauf wird beim Plugin-Reload geleert.** `InputHistoryService.Reset` hängt jetzt in `Plugin.DisposeAsync`
|
||||
neben den anderen Pure-Memory-Cleanups, damit der statische Zustand aus der vorigen Session den nächsten Load nicht
|
||||
mehr erbt
|
||||
- **Statusleiste klippt nicht mehr.** Der rechtsbündige Versions-Slot wird ausgeblendet wenn die Chat-Window-Breite
|
||||
abzüglich Versions-Text unter 200 px fällt — vorher überlappte er die vier linken Slots. Ab ausreichender Breite
|
||||
taucht der Slot wieder auf
|
||||
- **Intern:** `FontManager` fällt auf System-Font zurück wenn die eingebettete Hellion-Font-Resource fehlt
|
||||
(Broken-csproj-Pfad, nie ein Produktions-Build), plus expliziter Session-Only-Invariant-Kommentar für Auto-Tell-Tabs
|
||||
in `Plugin.cs:167-168` mit einem TempTabCounter-Init-Pin in der Build-Suite. Kein Schema-Bump, keine Migration
|
||||
- **Fehler-Benachrichtigung im Chat-Fenster.** Wenn ein Render-Fehler in `DrawChatLog` auftritt,
|
||||
zeigt das Plugin jetzt eine einmalige Warning-Notification mit Verweis aufs `/xllog`, statt das
|
||||
Fenster stillschweigend leer zu lassen. Der Stack-Trace selbst geht weiter via `Plugin.Log.Error`
|
||||
ins Logfile. De-Dup über Per-Session-Bool, damit ein wiederkehrender Fehler die Notification-Stack
|
||||
nicht pro Frame neu vollkippt
|
||||
- **First-Run-Wizard trennt Accept und Close.** `OnClose` setzt nicht mehr stillschweigend
|
||||
`FirstRunCompleted=true`, also lässt das X den Wizard schwebend zurück und er kommt beim nächsten
|
||||
Plugin-Reload wieder. Eine neue „Später — Defaults behalten"-Schaltfläche im Footer ist der
|
||||
explizite Weg, ohne Profil-Auswahl rauszukommen. Strings bilingual EN+DE plus Tooltip
|
||||
- **Eingabe-Verlauf wird beim Plugin-Reload geleert.** `InputHistoryService.Reset` hängt jetzt in
|
||||
`Plugin.DisposeAsync` neben den anderen Pure-Memory-Cleanups, damit der statische Zustand aus der
|
||||
vorigen Session den nächsten Load nicht mehr erbt
|
||||
- **Statusleiste klippt nicht mehr.** Der rechtsbündige Versions-Slot wird ausgeblendet wenn die
|
||||
Chat-Window-Breite abzüglich Versions-Text unter 200 px fällt — vorher überlappte er die vier
|
||||
linken Slots. Ab ausreichender Breite taucht der Slot wieder auf
|
||||
- **Intern:** `FontManager` fällt auf System-Font zurück wenn die eingebettete Hellion-Font-Resource
|
||||
fehlt (Broken-csproj-Pfad, nie ein Produktions-Build), plus expliziter
|
||||
Session-Only-Invariant-Kommentar für Auto-Tell-Tabs in `Plugin.cs:167-168` mit einem
|
||||
TempTabCounter-Init-Pin in der Build-Suite. Kein Schema-Bump, keine Migration
|
||||
|
||||
@@ -3,31 +3,34 @@ subtitle: Code Hygiene and Refactor
|
||||
versionsnatur: Maintenance-Cycle
|
||||
---
|
||||
|
||||
Wartungs-Patch ohne User-sichtbare Änderungen. Saubere Code-Basis als Vorbereitung auf das v1.4.7-Backlog-Cleanup, plus
|
||||
zwei geerbte Bugfixes aus dem ChatTwo-Upstream `f35b7d3`.
|
||||
Wartungs-Patch ohne User-sichtbare Änderungen. Saubere Code-Basis als Vorbereitung auf das
|
||||
v1.4.7-Backlog-Cleanup, plus zwei geerbte Bugfixes aus dem ChatTwo-Upstream `f35b7d3`.
|
||||
|
||||
- **preflight.sh härter**: csharpier-Reflow-Check (Block E) und markdownlint (Block F) laufen jetzt im Pre-Push-Gate,
|
||||
statt erst beim Pre-Merge-Review aufzufallen.
|
||||
- **FontManager-Fallback robuster**: Atlas-Toolkit-Throws aus kaputten Font-Configs (IO, InvalidOperation,
|
||||
ArgumentException) fallen jetzt zuverlässig auf NotoSansCjkRegular, statt den Atlas-Build mitzureißen. Der
|
||||
Exception-Typ wird im Log mitgegeben für die Diagnose.
|
||||
- **URL-Validation beim Plugin-Load**: BrandingLinks (5 URLs) und IntegrationLinks (2 URLs) werden via
|
||||
`[ModuleInitializer]` geprüft. Ein Tippfehler bei einer künftigen URL-Rotation wirft jetzt sofort beim Plugin-Load,
|
||||
statt still beim Klick zu scheitern.
|
||||
- **Cherry-Pick aus ChatTwo `f35b7d3`** — Memory-Leak in `Chat.SetChannel`: der native `Utf8String` wird jetzt auch dann
|
||||
freigegeben, wenn der Linkshell-Check den Channel ablehnt (vorher gefangen im early-return).
|
||||
- **Cherry-Pick aus ChatTwo `f35b7d3`** — `Tab.Clone()` Deep-cloned jetzt `UsedChannel` und `TellTarget`. Vorher
|
||||
Reference-Share-Bug: PopOut- und Temp-Tabs mutierten sich gegenseitig.
|
||||
- **preflight.sh härter**: csharpier-Reflow-Check (Block E) und markdownlint (Block F) laufen jetzt
|
||||
im Pre-Push-Gate, statt erst beim Pre-Merge-Review aufzufallen.
|
||||
- **FontManager-Fallback robuster**: Atlas-Toolkit-Throws aus kaputten Font-Configs (IO,
|
||||
InvalidOperation, ArgumentException) fallen jetzt zuverlässig auf NotoSansCjkRegular, statt den
|
||||
Atlas-Build mitzureißen. Der Exception-Typ wird im Log mitgegeben für die Diagnose.
|
||||
- **URL-Validation beim Plugin-Load**: BrandingLinks (5 URLs) und IntegrationLinks (2 URLs) werden
|
||||
via `[ModuleInitializer]` geprüft. Ein Tippfehler bei einer künftigen URL-Rotation wirft jetzt
|
||||
sofort beim Plugin-Load, statt still beim Klick zu scheitern.
|
||||
- **Cherry-Pick aus ChatTwo `f35b7d3`** — Memory-Leak in `Chat.SetChannel`: der native `Utf8String`
|
||||
wird jetzt auch dann freigegeben, wenn der Linkshell-Check den Channel ablehnt (vorher gefangen im
|
||||
early-return).
|
||||
- **Cherry-Pick aus ChatTwo `f35b7d3`** — `Tab.Clone()` Deep-cloned jetzt `UsedChannel` und
|
||||
`TellTarget`. Vorher Reference-Share-Bug: PopOut- und Temp-Tabs mutierten sich gegenseitig.
|
||||
- **Aktive-Tab-Underline pixel-perfect bei DPI-Scaling**: Die Underline-Pill skaliert jetzt mit
|
||||
`ImGuiHelpers.GlobalScale` und rundet die DrawList-Koordinaten auf physische Pixel. Kein Sub-Pixel-Blur mehr auf
|
||||
125/150%-Setups.
|
||||
- **IconButton-Width-Fix**: der manuelle `width - 2 * CellPadding.X`-Subtract verlor den HUD-Scale (Padding skaliert,
|
||||
der raw int nicht). Gemessene Breite läuft jetzt unverändert durch.
|
||||
- **Test-Isolation für MessageStore**: `Dalamud.Utility.Util`-Surface (IsWine, OpenLink) läuft jetzt durch eine
|
||||
`IPlatformUtil`-Indirektion. MessageStores `IsWine`-Probe ist isoliert testbar in der Build-Suite. Plus:
|
||||
HellionStyle-ChildBgAlpha als Pure-Helper extrahiert, Plugin.SaveConfig kopiert nur Session-Tabs statt der ganzen
|
||||
Tab-Liste, SettingsOverview cached den DrawList einmal pro Frame.
|
||||
- **Built-in-Theme-Roster**: Crystal Nocturne (Royal Sapphire + Electric Magenta auf Obsidian, von CRYSTALLITE) ersetzt
|
||||
Moonlit Bloom. User mit Moonlit Bloom als aktivem Theme fallen beim ersten Plugin-Load auf Hellion Arctic zurück.
|
||||
`ImGuiHelpers.GlobalScale` und rundet die DrawList-Koordinaten auf physische Pixel. Kein
|
||||
Sub-Pixel-Blur mehr auf 125/150%-Setups.
|
||||
- **IconButton-Width-Fix**: der manuelle `width - 2 * CellPadding.X`-Subtract verlor den HUD-Scale
|
||||
(Padding skaliert, der raw int nicht). Gemessene Breite läuft jetzt unverändert durch.
|
||||
- **Test-Isolation für MessageStore**: `Dalamud.Utility.Util`-Surface (IsWine, OpenLink) läuft jetzt
|
||||
durch eine `IPlatformUtil`-Indirektion. MessageStores `IsWine`-Probe ist isoliert testbar in der
|
||||
Build-Suite. Plus: HellionStyle-ChildBgAlpha als Pure-Helper extrahiert, Plugin.SaveConfig kopiert
|
||||
nur Session-Tabs statt der ganzen Tab-Liste, SettingsOverview cached den DrawList einmal pro
|
||||
Frame.
|
||||
- **Built-in-Theme-Roster**: Crystal Nocturne (Royal Sapphire + Electric Magenta auf Obsidian, von
|
||||
CRYSTALLITE) ersetzt Moonlit Bloom. User mit Moonlit Bloom als aktivem Theme fallen beim ersten
|
||||
Plugin-Load auf Hellion Arctic zurück.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
@@ -3,27 +3,31 @@ subtitle: Backlog Cleanup and Mid-Features
|
||||
versionsnatur: Mid-Feature-Patch
|
||||
---
|
||||
|
||||
Achter Sub-Patch der v1.4.x Polish-Sweep-Serie. Erstes User-sichtbares Feature-Bundle seit v1.4.5 — angepinnte Tell-Tabs
|
||||
die Relog überleben, opt-in Honorific-Glow, plus eine konfigurierbare Sidebar.
|
||||
Achter Sub-Patch der v1.4.x Polish-Sweep-Serie. Erstes User-sichtbares Feature-Bundle seit v1.4.5 —
|
||||
angepinnte Tell-Tabs die Relog überleben, opt-in Honorific-Glow, plus eine konfigurierbare Sidebar.
|
||||
|
||||
- **TempTell anpinnen**: Rechtsklick auf einen TempTell-Tab in der Sidebar → „Tab anpinnen". Angepinnte Tabs überleben
|
||||
Plugin-Reload und Char-Logout, behalten ihre Konversations-Historie (wird beim Rehydrate aus dem MessageStore
|
||||
nachgeladen) und bleiben an die gleiche /tell-Person gebunden. Hard-Cap 5 angepinnte Tabs in einem separaten Pool —
|
||||
die normalen Auto-Tell-Tabs (15er Cap) sind davon entkoppelt, Gesamt-Decke 20. Die Sidebar gruppiert angepinnte Tabs
|
||||
in einer eigenen „Angepinnt"-Sektion mit eigenem Trenner.
|
||||
- **Honorific Glow-Outline**: rendert jetzt eine 8-Richtungs-DrawList-Outline wenn der Honorific-Titel eine Glow-Farbe
|
||||
trägt. Opt-in via **Settings → Integrationen → Glow-Outline rendern (Honorific)** (Default OFF). Gradient (Color3 /
|
||||
GradientColourSet / Wave / Pulse) wird geparst und im DTO weitergereicht, rendert aktuell aber statisch als
|
||||
Primärfarbe — der volle Gradient-Port (Animations-Algorithmus + Pride-Palette) kommt als eigener Cycle nach.
|
||||
- **Sidebar-Breite konfigurierbar**: in **Theme & Layout** ein Slider 44–160 px. Default bleibt 44 px (icon-only), aber
|
||||
breiter machen damit Sektion-Header wie „Aktive Tells (3)" oder „Angepinnt (2)" nicht abgeschnitten werden.
|
||||
- **Settings-Save Channel-Fix**: ein Save mit aktivem Party- oder Linkshell-Tab konnte den Chat-Input zurück auf
|
||||
`/tell <angepinnte Person>` springen lassen. `Configuration.UpdateFrom` bewahrt jetzt den Runtime-`CurrentChannel`
|
||||
über den persistent-Tab-Merge hinweg, und `TabSwitched` deep-cloned den Seed-Channel statt sich den `UsedChannel` mit
|
||||
dem vorigen Tab zu teilen.
|
||||
- **Internal**: `IPluginLogProxy`-Indirektion vor Dalamud's `IPluginLog` über alle ~91 `Plugin.Log`-Call-Sites. Damit
|
||||
läuft `MessageStore.Migrate0` voll-isoliert in xUnit (F12.1-Lücke aus v1.4.6 geschlossen). Plus: TempTab-Counter als
|
||||
abgeleitete Property statt gecachtes Interlocked-Feld — die neuen Pin/Unpin-Übergänge sind Cold-Path, kein
|
||||
Lock-Free-Vorteil mehr. Migration v16 → v17 ist rein additiv (neues `Tab.IsPinned`-Bool, Default false).
|
||||
- **TempTell anpinnen**: Rechtsklick auf einen TempTell-Tab in der Sidebar → „Tab anpinnen".
|
||||
Angepinnte Tabs überleben Plugin-Reload und Char-Logout, behalten ihre Konversations-Historie
|
||||
(wird beim Rehydrate aus dem MessageStore nachgeladen) und bleiben an die gleiche /tell-Person
|
||||
gebunden. Hard-Cap 5 angepinnte Tabs in einem separaten Pool — die normalen Auto-Tell-Tabs (15er
|
||||
Cap) sind davon entkoppelt, Gesamt-Decke 20. Die Sidebar gruppiert angepinnte Tabs in einer
|
||||
eigenen „Angepinnt"-Sektion mit eigenem Trenner.
|
||||
- **Honorific Glow-Outline**: rendert jetzt eine 8-Richtungs-DrawList-Outline wenn der
|
||||
Honorific-Titel eine Glow-Farbe trägt. Opt-in via **Settings → Integrationen → Glow-Outline
|
||||
rendern (Honorific)** (Default OFF). Gradient (Color3 / GradientColourSet / Wave / Pulse) wird
|
||||
geparst und im DTO weitergereicht, rendert aktuell aber statisch als Primärfarbe — der volle
|
||||
Gradient-Port (Animations-Algorithmus + Pride-Palette) kommt als eigener Cycle nach.
|
||||
- **Sidebar-Breite konfigurierbar**: in **Theme & Layout** ein Slider 44–160 px. Default bleibt 44
|
||||
px (icon-only), aber breiter machen damit Sektion-Header wie „Aktive Tells (3)" oder „Angepinnt
|
||||
(2)" nicht abgeschnitten werden.
|
||||
- **Settings-Save Channel-Fix**: ein Save mit aktivem Party- oder Linkshell-Tab konnte den
|
||||
Chat-Input zurück auf `/tell <angepinnte Person>` springen lassen. `Configuration.UpdateFrom`
|
||||
bewahrt jetzt den Runtime-`CurrentChannel` über den persistent-Tab-Merge hinweg, und `TabSwitched`
|
||||
deep-cloned den Seed-Channel statt sich den `UsedChannel` mit dem vorigen Tab zu teilen.
|
||||
- **Internal**: `IPluginLogProxy`-Indirektion vor Dalamud's `IPluginLog` über alle ~91
|
||||
`Plugin.Log`-Call-Sites. Damit läuft `MessageStore.Migrate0` voll-isoliert in xUnit (F12.1-Lücke
|
||||
aus v1.4.6 geschlossen). Plus: TempTab-Counter als abgeleitete Property statt gecachtes
|
||||
Interlocked-Feld — die neuen Pin/Unpin-Übergänge sind Cold-Path, kein Lock-Free-Vorteil mehr.
|
||||
Migration v16 → v17 ist rein additiv (neues `Tab.IsPinned`-Bool, Default false).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
@@ -3,19 +3,17 @@ subtitle: Hook-Layer und Polish-Quick-Wins
|
||||
versionsnatur: Polish-Patch
|
||||
---
|
||||
|
||||
- DbViewer Volltext-Suche: optionaler FTS5-Index über die ganze Chat-Historie.
|
||||
Wird beim ersten v1.4.8-Start asynchron im Hintergrund gebaut, Progress als
|
||||
Toast. Lokale Page-Suche bleibt Default. Such-Eingaben werden als exakte
|
||||
Wortfolge gematcht; mehrere Wörter werden nur gefunden, wenn sie zusammen
|
||||
und in der Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt
|
||||
- DbViewer Volltext-Suche: optionaler FTS5-Index über die ganze Chat-Historie. Wird beim ersten
|
||||
v1.4.8-Start asynchron im Hintergrund gebaut, Progress als Toast. Lokale Page-Suche bleibt
|
||||
Default. Such-Eingaben werden als exakte Wortfolge gematcht; mehrere Wörter werden nur gefunden,
|
||||
wenn sie zusammen und in der Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt
|
||||
eigene Anführungszeichen um den Suchbegriff.
|
||||
- Custom-Theme-Files laden sich beim Speichern automatisch neu, wenn das Theme
|
||||
aktiv ist. Kein Picker-Klick mehr nötig.
|
||||
- Retention-Sweep blockt nicht mehr den Framework-Thread. Der Mini-Hitch von
|
||||
~194ms pro Sweep ist weg.
|
||||
- Custom-Theme-Files laden sich beim Speichern automatisch neu, wenn das Theme aktiv ist. Kein
|
||||
Picker-Klick mehr nötig.
|
||||
- Retention-Sweep blockt nicht mehr den Framework-Thread. Der Mini-Hitch von ~194ms pro Sweep ist
|
||||
weg.
|
||||
- Statusleiste rendert sauber bei Windows-Skalierung über 100%.
|
||||
- Receive-Suppressed-Tells-Routing wurde in diesem Cycle untersucht und auf
|
||||
v1.5.x verschoben: wenn andere Plugins Tells via CheckMessageHandled
|
||||
unterdrücken, überspringt FFXIVs Chat-Pipeline den RaptureLogModule-Resolver
|
||||
und HellionChats Tab-Routing verliert den Tell-Partner. Der Fix liegt
|
||||
- Receive-Suppressed-Tells-Routing wurde in diesem Cycle untersucht und auf v1.5.x verschoben: wenn
|
||||
andere Plugins Tells via CheckMessageHandled unterdrücken, überspringt FFXIVs Chat-Pipeline den
|
||||
RaptureLogModule-Resolver und HellionChats Tab-Routing verliert den Tell-Partner. Der Fix liegt
|
||||
architektonisch neben dem geplanten Ad-Block-Hook-Layer und kommt dort mit.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
subtitle: Plugin-Load Render Polish
|
||||
versionsnatur: Performance-Patch
|
||||
---
|
||||
|
||||
- First-Frame-HITCH unter 100 ms: der erste Render-Frame des Plugins liegt jetzt bei ~76 ms Median
|
||||
(vorher ~127 ms), die Dalamud-Warnung „UiBuilder(Hellion Chat) > 100ms" beim Plugin-Start ist
|
||||
damit weg. Erreicht durch das Verlagern von sechs nicht-essentiellen Render- Sektionen
|
||||
(Statusleiste, Kanalname-Chunks, Fenster-Bounds-Check, Hinweis-Banner, Autocomplete,
|
||||
Input-Preview) auf den zweiten Frame. Bei 60 fps sieht man die deferred-Sektionen ~17 ms später,
|
||||
was im Atlas-Build-Fenster nach einem Reload unsichtbar bleibt.
|
||||
- Slash-Commands zentral registriert: /hellion, /hellionView, /hellionSeString und /hellionDebugger
|
||||
werden jetzt im Plugin-Load zentral registriert statt erst beim ersten Öffnen ihres Ziel-Fensters.
|
||||
Heißt: die Befehle funktionieren ab dem ersten Tick, auch wenn das jeweilige Fenster nie geöffnet
|
||||
wurde. Der „Einstellungen"-Button im Plugin-Manager hängt am selben Pfad.
|
||||
- Plugin-Load-Diagnose-Logs als Tripwire: die Profiling-Logs für MessageStore.Connect,
|
||||
MessageStore.Migrate, FilterAllTabs und den Auto-Translate-Warmup bleiben auf Information-Level
|
||||
eingeschaltet. Falls eine zukünftige Änderung die Lade-Zeit wieder über 100 ms drückt, taucht der
|
||||
Mehrverbrauch direkt im /xllog auf, ohne dass jemand erst den Debug-Filter einschalten muss.
|
||||
- ChatTwo-IPC-Kompatibilitäts-Layer: HellionChat spiegelt jetzt die komplette ChatTwo-IPC-Surface
|
||||
(`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`, `Invoke`)
|
||||
zusätzlich zu unseren eigenen `HellionChat.*`-Gates unter dem `ChatTwo.*`-Namensraum. Drittseitige
|
||||
Integrationen die nur auf ChatTwo's IPC reagieren, etwa die Kontextmenü-Hooks von Artisan und
|
||||
AllaganTools, funktionieren damit weiter ohne Code-Änderung auf ihrer Seite. Die
|
||||
Conflict-Detection blockiert das parallele Laden von ChatTwo, daher kein Namensraum-Konflikt im
|
||||
Live-Betrieb.
|
||||
- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations- Aufwand. Nach dem Update
|
||||
läuft das Plugin gegen die bestehende v17-Datenbank weiter.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
subtitle: DI Foundation und Service-Refactor
|
||||
versionsnatur: Architektur-Cycle
|
||||
---
|
||||
|
||||
- **Architektur-Umbau ohne User-spürbare Verhaltens-Änderung:** der Plugin-Bootstrap wechselt auf
|
||||
einen Generic-Host DI-Container (`Microsoft.Extensions.Hosting` + `IServiceCollection`) nach dem
|
||||
Lightless-Sync-Muster. 18 Service-Klassen wandern von einem statischen `Plugin.LogProxy`-Locator
|
||||
auf typisierte `ILogger<T>`-Constructor-Injection. `DalamudLogger` brückt
|
||||
`Microsoft.Extensions.Logging` über auf Dalamuds `IPluginLog` — im xllog erscheinen jetzt
|
||||
Service-spezifische Spalten wie `[ MessageManager]` und `[Honori...ervice]`.
|
||||
- **Plugin.LogProxy bleibt für die acht Buckets erhalten,** die Constructor-Injection nicht
|
||||
erreicht: Static-Helper (EmoteCache, AutoTranslate, MemoryUtil, WrapperUtil), Dalamud-Reflektion
|
||||
(Configuration), Data-Class mit Massen-Instanziierung (Message) und Instanz-Klassen die nur aus
|
||||
Static-Methods loggen (FontManager, eine GameFunctions-Stelle).
|
||||
- **Performance bestätigt durch Cross-Plugin-Baseline:** HellionChat First-Frame-HITCH 77 ms Median,
|
||||
Chat 2 v1.40.2 74 ms Median — kein DI-Penalty gegenüber dem Upstream-Fork-Origin. Lightless und
|
||||
XIVInstantMessenger liegen bei ~7 ms weil sie ihren FontAtlas-Build deferren; das wird das
|
||||
v1.5.1-Item.
|
||||
- **User-sichtbarer Bug-Fix nebenbei:** Slash-Command-Einfügen in das Chat-Eingabefeld (Friend-List
|
||||
"/tell"-Action plus Plugin-Inserts von Artisan, AllaganTools und ähnlichen) ersetzt jetzt den
|
||||
vorhandenen Input, statt anzukonkatenieren. Cherry-Pick aus ChatTwo upstream `ee7768ac` mit
|
||||
Namespace-Anpassung.
|
||||
- **Foundation für die Plugin-Integrations-Wave:** v1.5.7-11 (Context-Menu, NotificationMaster,
|
||||
Moodles, ExtraChat, XIVIM Quick-DM) werden ab jetzt strukturell handhabbar — neue Services sind
|
||||
ein `services.AddSingleton<T>` plus ein paar Factory-Lambda- Zeilen, kein Plugin.cs-Anflanschen
|
||||
mehr.
|
||||
- Migration v17 unverändert: kein Schema-Bump, kein Config-Migrations-Aufwand.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
subtitle: "FontAtlas Refactor and Forge Signature"
|
||||
versionsnatur: "Architecture + Closure + Branding"
|
||||
---
|
||||
|
||||
- **FontManager-Refactor.** Der FontAtlas baut jetzt nur noch einmal pro Plugin-Load statt vier- bis
|
||||
fünfmal. Weniger CPU- und GPU-Druck in den ersten Sekunden nach einem Reload, weniger
|
||||
Atlas-Texture-Memory-Churn. Die acht Font-Einstellungen können live über den neuen
|
||||
`RebuildDelegateFonts`-Pfad geändert werden, ohne dass das Plugin neu geladen werden muss.
|
||||
- **Hellion Forge Signatur.** Das Plugin trägt jetzt eine ASCII-Fuchs-Signatur. Im `/xllog`
|
||||
erscheint beim Plugin-Load ein kleiner Fuchs-Kopf, im First-Run-Wizard und unter Settings →
|
||||
Information taucht eine eingeklappte „Hellion Forge"-Sektion mit dem vollen Fuchs auf. Gezeichnet
|
||||
von Julia Moon, fest in der Plugin-DLL eingebettet.
|
||||
- **Honorific-Integration bleibt unverändert.** Der ursprünglich geplante Gradient-Render-Pfad
|
||||
(Wave/Pulse-Animation) entfällt. Honorific 3.2 stellt keine IPC für den fertig gerenderten
|
||||
Gradient-Frame zur Verfügung, und ein eigener Port der Pride-Palette wurde verworfen. Die
|
||||
Honorific-Anzeige bleibt wie in v1.4.7 etabliert (statischer Glow plus Title).
|
||||
- **Hinweis zum HITCH-Win.** Der ursprünglich angepeilte 10×-First-Frame-Sprung
|
||||
(Lightless/XIVIM-Pattern, ~7 ms statt ~75 ms) ist in diesem Cycle nicht eingetreten. Die
|
||||
Render-Kosten liegen im UiBuilder-First-Frame-Pfad, nicht im FontAtlas-Build. Investigation kommt
|
||||
als eigener späterer Cycle. Keine User-sichtbare Disruption, keine Migration.
|
||||
+14
-12
@@ -2,26 +2,28 @@
|
||||
|
||||
## How to install
|
||||
|
||||
This release is distributed via the HellionChat custom repository, not the Dalamud main plugin repo. To install:
|
||||
This release is distributed via the HellionChat custom repository, not the Dalamud main plugin repo.
|
||||
To install:
|
||||
|
||||
1. In XIVLauncher: **Settings → Experimental → Custom Plugin Repositories**
|
||||
2. Add the URL: `https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||
2. Add the URL:
|
||||
`https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/repo.json`
|
||||
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
||||
|
||||
## Project documents
|
||||
|
||||
- [README](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/README.md) — features,
|
||||
architecture, build
|
||||
- [Privacy notice](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md) — what
|
||||
the plugin stores and sends
|
||||
- [README](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/README.md)
|
||||
— features, architecture, build
|
||||
- [Privacy notice](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/PRIVACY.md)
|
||||
— what the plugin stores and sends
|
||||
- [Third-party notices](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/docs/THIRD_PARTY_NOTICES.md)
|
||||
— dependencies and licences
|
||||
- [Security policy](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md) —
|
||||
vulnerability reporting
|
||||
- [Support](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md) — bug reports,
|
||||
questions, contact paths
|
||||
- [Security policy](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SECURITY.md)
|
||||
— vulnerability reporting
|
||||
- [Support](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/SUPPORT.md)
|
||||
— bug reports, questions, contact paths
|
||||
|
||||
## Licence
|
||||
|
||||
[EUPL-1.2](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE). Based on
|
||||
[Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna, also EUPL-1.2.
|
||||
[EUPL-1.2](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/src/branch/main/LICENSE).
|
||||
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna, also EUPL-1.2.
|
||||
|
||||
+483
-256
@@ -1,37 +1,193 @@
|
||||
## 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
|
||||
## .gitignore – Hellion Forge / Hellion Media
|
||||
##
|
||||
## Basis: github/gitignore VisualStudio.gitignore
|
||||
## Überarbeitet: Mai 2026
|
||||
## Status: Original-Patterns vollständig erhalten,
|
||||
## neu sortiert in logische Sektionen,
|
||||
## Sicherheits- & Tooling-Sektionen ergänzt.
|
||||
##
|
||||
## Markierungen:
|
||||
## [!! OBSOLET 2026 !!] → Tool offiziell eingestellt,
|
||||
## Pattern bleibt aus Vorsicht drin.
|
||||
##
|
||||
##############################################################
|
||||
|
||||
# Local development environment (HellionChat fork)
|
||||
|
||||
# =====================================================
|
||||
# [!! KRITISCH !!] Secrets, Keys & Credentials
|
||||
# Diese Sachen dürfen NIEMALS im Repo landen!
|
||||
# =====================================================
|
||||
|
||||
# Environment Files
|
||||
.env
|
||||
.env.*
|
||||
.env.bak*
|
||||
.envrc
|
||||
!.env.example
|
||||
!.env.sample
|
||||
|
||||
# Private Keys & Zertifikate
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
*.cer
|
||||
*.crt
|
||||
*.csr
|
||||
*.gpg
|
||||
*.asc
|
||||
|
||||
# SSH Keys (falls jemand die ins Repo legt)
|
||||
id_rsa
|
||||
id_ed25519
|
||||
id_ecdsa
|
||||
known_hosts
|
||||
|
||||
# Auth-/Token-Files
|
||||
auth.json
|
||||
.npmrc
|
||||
.pypirc
|
||||
secrets.json
|
||||
|
||||
# ASP.NET / .NET App-Configs mit lokalen Secrets
|
||||
appsettings.*.local.json
|
||||
appsettings.Local.json
|
||||
local.settings.json
|
||||
|
||||
# Memory Dumps (können Credentials im Heap enthalten!)
|
||||
*.dmp
|
||||
*.mdmp
|
||||
crash.log
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Projekt-spezifisch (HellionChat Fork)
|
||||
# =====================================================
|
||||
|
||||
# Lokale Entwicklungsumgebung
|
||||
.vscode/
|
||||
scripts/setup-dev-env.sh
|
||||
|
||||
# Local test project (stays out of the published plugin repo;
|
||||
# pure-function safety net for refactor cycles)
|
||||
# Lokales Test-Projekt (bleibt aus dem Plugin-Repo raus;
|
||||
# pure-function safety net für Refactor-Cycles)
|
||||
HellionChat.Tests/
|
||||
ChatTwo.Tests
|
||||
TestResults
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# Packaging
|
||||
pack/
|
||||
|
||||
# User-specific files
|
||||
# Specs und Plan-Dateien
|
||||
/.superpowers/
|
||||
|
||||
# Claude Code lokales Setup (nicht committed)
|
||||
/.claude/
|
||||
/CLAUDE.md
|
||||
|
||||
# Cycle-Working-Notes (im Vault gepflegt, lokales Repo-Pad bei Bedarf)
|
||||
/docs/cycle-notes/
|
||||
|
||||
|
||||
# =====================================================
|
||||
# OS-spezifische Files
|
||||
# =====================================================
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Linux
|
||||
.directory
|
||||
.Trash-*
|
||||
|
||||
|
||||
# =====================================================
|
||||
# AI / LLM Tooling (2026 era)
|
||||
# =====================================================
|
||||
|
||||
# Cursor IDE
|
||||
.cursor/
|
||||
.cursorignore
|
||||
|
||||
# Aider
|
||||
.aider*
|
||||
|
||||
# Continue.dev
|
||||
.continue/
|
||||
.continuerc.json
|
||||
|
||||
# Windsurf
|
||||
.windsurf/
|
||||
|
||||
# Sourcegraph Cody
|
||||
.cody/
|
||||
|
||||
# Lokale Prompt-Sammlungen / Scratch-Pads
|
||||
prompts/local/
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Editor & IDE (neben Visual Studio)
|
||||
# =====================================================
|
||||
|
||||
# JetBrains (IntelliJ, Rider, etc.)
|
||||
.idea/
|
||||
|
||||
# Vim / Neovim
|
||||
*.swp
|
||||
*.swo
|
||||
*.swn
|
||||
|
||||
# Sublime Text
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
|
||||
# =====================================================
|
||||
# IDE & Editor – User-spezifische Files (VS)
|
||||
# =====================================================
|
||||
|
||||
# Visual Studio User Files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
# MonoDevelop/Xamarin Studio
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
# Visual Studio 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/
|
||||
|
||||
# Local History
|
||||
.localhistory/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Build Output
|
||||
# =====================================================
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
@@ -47,43 +203,24 @@ bld/
|
||||
[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
|
||||
# ATL Project Build Output
|
||||
[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
|
||||
# MigrationBackup (Package Reference Convert Tool)
|
||||
MigrationBackup/
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
# =====================================================
|
||||
# Build-Artefakte (Files built by Visual Studio)
|
||||
# =====================================================
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
@@ -105,6 +242,7 @@ StyleCopReport.xml
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.binlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
@@ -112,10 +250,87 @@ StyleCopReport.xml
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
|
||||
# =====================================================
|
||||
# Test Results
|
||||
# =====================================================
|
||||
|
||||
# MSTest
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# Verify / Snapshot Testing (modern .NET, Spotty Wisdom)
|
||||
*.received.*
|
||||
*.received.txt
|
||||
|
||||
# [!! OBSOLET 2026 !!] Chutzpah – Repository auf GitHub archiviert
|
||||
_Chutzpah*
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Code Coverage
|
||||
# =====================================================
|
||||
|
||||
# Coverlet
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# DotCover (JetBrains)
|
||||
*.dotCover
|
||||
|
||||
# AxoCover
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# OpenCover UI Analysis
|
||||
OpenCover/
|
||||
|
||||
# [!! OBSOLET 2026 !!] MightyMoose / AutoTest.Net – seit >10 Jahren nicht mehr gepflegt
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Profiler & Trace
|
||||
# =====================================================
|
||||
|
||||
# Visual Studio Profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# NVidia Nsight GPU Debugger
|
||||
*.nvuser
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Cache Files (VS, C++, Sass)
|
||||
# =====================================================
|
||||
|
||||
# Visual C++ cache files
|
||||
# Hinweis: Manche Patterns hier werden auch vom C#-Linter genutzt (z. B. *.lscache)
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
@@ -125,101 +340,80 @@ ipch/
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
*.lscache
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
# Visual Studio cache (.cache files allgemein, .cache directories behalten)
|
||||
*.[Cc]ache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# 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)
|
||||
# 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 & Dependencies
|
||||
# =====================================================
|
||||
|
||||
# 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
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Fody – auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# Node (falls JS-Tooling im Build genutzt wird)
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Python Tools für Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Mono
|
||||
# =====================================================
|
||||
|
||||
mono_crash.*
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Publish & Deploy
|
||||
# =====================================================
|
||||
|
||||
# Click-Once
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
*.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/
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
@@ -228,7 +422,35 @@ csx/
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
# Service Fabric Backup
|
||||
ServiceFabricBackup/
|
||||
|
||||
# Installshield
|
||||
[Ee]xpress/
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Container / Infrastructure-as-Code (Vorsicht: Tokens!)
|
||||
# =====================================================
|
||||
|
||||
# Docker
|
||||
docker-compose.override.yml
|
||||
|
||||
# Terraform
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
*.tfvars
|
||||
!example.tfvars
|
||||
|
||||
# Serverless Framework
|
||||
.serverless/
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Windows Store / AppX
|
||||
# =====================================================
|
||||
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
@@ -237,23 +459,155 @@ _pkginfo.txt
|
||||
*.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/
|
||||
~$*
|
||||
*~
|
||||
# =====================================================
|
||||
# Datenbanken & SQL
|
||||
# =====================================================
|
||||
|
||||
# SQL Server
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Andere DB-bezogene
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
|
||||
# [!! OBSOLET 2026 !!] BeatPulse – wurde 2019 umbenannt zu AspNetCore.Diagnostics.HealthChecks
|
||||
healthchecksdb
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Business Intelligence / Reporting
|
||||
# =====================================================
|
||||
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
*.rptproj.bak
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Add-ins & Analyzer Tools
|
||||
# =====================================================
|
||||
|
||||
# ReSharper
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity
|
||||
_TeamCity*
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# [!! OBSOLET 2026 !!] GhostDoc Plugin – Submain hat das Tool eingestellt
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik JustMock
|
||||
*.jmconfig
|
||||
|
||||
# MFractors (Xamarin productivity tool)
|
||||
.mfractor/
|
||||
|
||||
# DocProject Documentation Generator
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Sonstige Sprachen & Tooling
|
||||
# =====================================================
|
||||
|
||||
# Ionide (F# VS Code Tools)
|
||||
.ionide/
|
||||
|
||||
# Azure Stream Analytics Local Run
|
||||
ASALocalRun/
|
||||
|
||||
# BizTalk Build Output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# Orleans
|
||||
orleans.codegen.cs
|
||||
|
||||
|
||||
# =====================================================
|
||||
# [!! OBSOLET 2026 !!] Legacy-Tooling (eingestellt)
|
||||
# Patterns bleiben aus Vorsicht drin.
|
||||
# =====================================================
|
||||
|
||||
# [!! OBSOLET 2026 !!] TFS 2012 Local Workspace – ersetzt durch Azure DevOps
|
||||
$tf/
|
||||
|
||||
# [!! OBSOLET 2026 !!] Visual Studio 6 Build Log – VS6 ist von 1998
|
||||
*.plg
|
||||
|
||||
# [!! OBSOLET 2026 !!] Visual Studio 6 Workspace Options
|
||||
*.opt
|
||||
|
||||
# [!! OBSOLET 2026 !!] Visual Studio 6 Workspace File
|
||||
*.vbw
|
||||
|
||||
# [!! OBSOLET 2026 !!] RIA / Silverlight – Microsoft hat das Okt. 2021 eingestellt
|
||||
Generated_Code/
|
||||
|
||||
# [!! OBSOLET 2026 !!] Visual Studio LightSwitch – von Microsoft eingestellt
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Upgrade / Backup-Reports
|
||||
# =====================================================
|
||||
|
||||
# Backup-Files vom Konvertieren alter VS-Projekte (wir haben ja git ;-))
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
|
||||
# =====================================================
|
||||
# Misc / Temp / Backup
|
||||
# =====================================================
|
||||
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.publishsettings
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
@@ -261,130 +615,3 @@ orleans.codegen.cs
|
||||
# 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
|
||||
|
||||
#Specs und Plan datein
|
||||
/.superpowers/
|
||||
|
||||
#Test Datein
|
||||
ChatTwo.Tests
|
||||
TestResults
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# Claude Code projekt-spezifisches Setup (lokal, nicht committed)
|
||||
/.claude/
|
||||
/CLAUDE.md
|
||||
|
||||
+10
-2
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"MD007": { "indent": 4 },
|
||||
"MD003": { "style": "atx" },
|
||||
"MD004": { "style": "dash" },
|
||||
"MD007": { "indent": 2 },
|
||||
"MD009": { "br_spaces": 2, "strict": false, "list_item_empty_lines": false },
|
||||
"MD013": false,
|
||||
"MD024": { "siblings_only": true },
|
||||
"MD029": false,
|
||||
"MD033": false,
|
||||
"MD036": false,
|
||||
"MD041": false
|
||||
"MD040": true,
|
||||
"MD041": false,
|
||||
"MD046": { "style": "fenced" },
|
||||
"MD048": { "style": "backtick" },
|
||||
"MD049": { "style": "underscore" },
|
||||
"MD050": { "style": "asterisk" }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,50 @@
|
||||
# ##############################################################
|
||||
# #
|
||||
# # .prettierignore – Hellion Forge / Hellion Media
|
||||
# #
|
||||
# # Files die Prettier NICHT anfassen soll.
|
||||
# # Überarbeitet: Mai 2026
|
||||
# #
|
||||
# # Hinweis: Prettier liest auch .gitignore automatisch mit.
|
||||
# # Hier nur Sachen die zusätzlich ignoriert werden müssen
|
||||
# # oder die im Repo liegen aber nicht formatiert werden dürfen.
|
||||
# #
|
||||
# ##############################################################
|
||||
|
||||
|
||||
# === .NET Build Output ===
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# === JS / Web Build Output ===
|
||||
node_modules/
|
||||
dist/
|
||||
out/
|
||||
build/
|
||||
coverage/
|
||||
|
||||
# === Generierte C#-Files (Designer, Source Generators) ===
|
||||
*.Designer.cs
|
||||
*.g.cs
|
||||
*.g.i.cs
|
||||
*.generated.cs
|
||||
*.AssemblyInfo.cs
|
||||
*.AssemblyAttributes.cs
|
||||
|
||||
# === Lock-Files (NIE umformatieren – zerschießt den Hash) ===
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
packages.lock.json
|
||||
|
||||
# === Minified Files (bewusst kompakt, niemals anfassen) ===
|
||||
*.min.js
|
||||
*.min.css
|
||||
|
||||
# === Test-Snapshots (z. B. Verify) ===
|
||||
*.received.*
|
||||
*.verified.*
|
||||
**/__snapshots__/
|
||||
|
||||
# === Plugin-Manifest (DalamudPackager-Schema, fix lassen) ===
|
||||
HellionChat/HellionChat.yaml
|
||||
|
||||
+30
-2
@@ -1,7 +1,35 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"proseWrap": "always",
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"endOfLine": "lf"
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "always",
|
||||
"endOfLine": "lf",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.yml", "*.yaml"],
|
||||
"options": {
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+51
-6
@@ -1,8 +1,53 @@
|
||||
# ##############################################################
|
||||
# #
|
||||
# # .yamllint.yaml – Hellion Forge / Hellion Media
|
||||
# #
|
||||
# # YAML-Linting Konfiguration.
|
||||
# # Überarbeitet: Mai 2026
|
||||
# #
|
||||
# # Regel-Doku:
|
||||
# # https://yamllint.readthedocs.io/en/stable/rules.html
|
||||
# #
|
||||
# ##############################################################
|
||||
|
||||
extends: default
|
||||
|
||||
# Plugin-Manifest folgt DalamudPackager-Konvention (4-space-indent für
|
||||
# image_urls + tags). yamllint-Default verlangt 2 — Konflikt, daher
|
||||
# ignorieren statt das Manifest zu reformatieren.
|
||||
ignore: |
|
||||
HellionChat/HellionChat.yaml
|
||||
|
||||
rules:
|
||||
line-length: disable
|
||||
document-start: disable
|
||||
truthy:
|
||||
allowed-values: ["true", "false", "on"]
|
||||
empty-lines:
|
||||
max: 1
|
||||
# Zeilenlängen-Check aus (konsistent mit markdownlint MD013)
|
||||
line-length: disable
|
||||
|
||||
# YAML ohne führendes "---" erlaubt
|
||||
document-start: disable
|
||||
|
||||
# GitHub Actions nutzt "on:" als Trigger-Key.
|
||||
# Ohne diesen Override würde yamllint das als boolean "on" beklagen.
|
||||
truthy:
|
||||
allowed-values: ['true', 'false', 'on']
|
||||
|
||||
# Maximal 1 Leerzeile in Folge (saubere Files)
|
||||
empty-lines:
|
||||
max: 1
|
||||
|
||||
# YAML-Standard ist 2 Spaces (auch GitHub Actions erwartet das).
|
||||
# Explizit setzen, um Konsistenz im Repo zu erzwingen.
|
||||
indentation:
|
||||
spaces: 2
|
||||
indent-sequences: true
|
||||
check-multi-line-strings: false
|
||||
|
||||
# Kommentare brauchen Space nach #, müssen mit Content beginnen
|
||||
comments:
|
||||
require-starting-space: true
|
||||
min-spaces-from-content: 1
|
||||
|
||||
# Kein Whitespace am Zeilenende
|
||||
trailing-spaces: enable
|
||||
|
||||
# Datei muss mit Newline enden
|
||||
new-line-at-end-of-file: enable
|
||||
|
||||
+48
-44
@@ -2,14 +2,15 @@
|
||||
|
||||
## A Note on This Project
|
||||
|
||||
HellionChat is a one-person side project developed under Hellion Forge. I maintain this in my spare time, which means
|
||||
replies can take a few days. Please do not escalate just because a thread is quiet.
|
||||
HellionChat is a one-person side project developed under Hellion Forge. I maintain this in my spare
|
||||
time, which means replies can take a few days. Please do not escalate just because a thread is
|
||||
quiet.
|
||||
|
||||
When in doubt, assume good intent. Contributors come from different backgrounds, time zones and skill levels. A
|
||||
clarifying question is almost always a better first move than an accusation.
|
||||
When in doubt, assume good intent. Contributors come from different backgrounds, time zones and
|
||||
skill levels. A clarifying question is almost always a better first move than an accusation.
|
||||
|
||||
Please also keep discussions on topic. This project is about a Dalamud chat plugin. Off-topic arguments belong
|
||||
elsewhere.
|
||||
Please also keep discussions on topic. This project is about a Dalamud chat plugin. Off-topic
|
||||
arguments belong elsewhere.
|
||||
|
||||
---
|
||||
|
||||
@@ -17,20 +18,21 @@ elsewhere.
|
||||
|
||||
We pledge to make our community welcoming, safe, and equitable for all.
|
||||
|
||||
We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all
|
||||
individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics,
|
||||
neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or
|
||||
religion, national or social origin, socio-economic position, level of education, or other status. The same privileges
|
||||
of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
|
||||
We are committed to fostering an environment that respects and promotes the dignity, rights, and
|
||||
contributions of all individuals, regardless of characteristics including race, ethnicity, caste,
|
||||
color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or
|
||||
expression, sexual orientation, language, philosophy or religion, national or social origin,
|
||||
socio-economic position, level of education, or other status. The same privileges of participation
|
||||
are extended to everyone who participates in good faith and in accordance with this Covenant.
|
||||
|
||||
## Encouraged Behaviors
|
||||
|
||||
While acknowledging differences in social norms, we all strive to meet our community's expectations for positive
|
||||
behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture,
|
||||
background, or native language.
|
||||
While acknowledging differences in social norms, we all strive to meet our community's expectations
|
||||
for positive behavior. We also understand that our words and actions may be interpreted differently
|
||||
than we intend based on culture, background, or native language.
|
||||
|
||||
With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared
|
||||
values, including:
|
||||
With these considerations in mind, we agree to behave mindfully toward each other and act in ways
|
||||
that center our shared values, including:
|
||||
|
||||
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
|
||||
2. Engaging **kindly and honestly** with others.
|
||||
@@ -42,31 +44,32 @@ values, including:
|
||||
|
||||
## Restricted Behaviors
|
||||
|
||||
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are
|
||||
violations of this Code of Conduct.
|
||||
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of
|
||||
these behaviors are violations of this Code of Conduct.
|
||||
|
||||
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any
|
||||
clear request to stop.
|
||||
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of
|
||||
people.
|
||||
3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis of immutable
|
||||
identities or traits.
|
||||
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or
|
||||
purpose of the community.
|
||||
5. **Violating confidentiality.** Sharing or acting on someone's personal or private information without their
|
||||
permission.
|
||||
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
|
||||
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal
|
||||
attention after any clear request to stop.
|
||||
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a
|
||||
community member or group of people.
|
||||
3. **Stereotyping or discrimination.** Characterizing anyone's personality or behavior on the basis
|
||||
of immutable identities or traits.
|
||||
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate
|
||||
in the context or purpose of the community.
|
||||
5. **Violating confidentiality.** Sharing or acting on someone's personal or private information
|
||||
without their permission.
|
||||
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person
|
||||
or group.
|
||||
7. Behaving in other ways that **threaten the well-being** of our community.
|
||||
|
||||
### Other Restrictions
|
||||
|
||||
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade
|
||||
enforcement actions.
|
||||
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone
|
||||
else to evade enforcement actions.
|
||||
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
|
||||
3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside the norms of the
|
||||
community.
|
||||
4. **Irresponsible communication.** Failing to responsibly present content which includes, links to, or describes any
|
||||
other restricted behaviors.
|
||||
3. **Promotional materials.** Sharing marketing or other commercial content in a way that is outside
|
||||
the norms of the community.
|
||||
4. **Irresponsible communication.** Failing to responsibly present content which includes, links to,
|
||||
or describes any other restricted behaviors.
|
||||
|
||||
## Reporting
|
||||
|
||||
@@ -77,12 +80,13 @@ If something here is being broken, contact me directly. Do not open a public iss
|
||||
| Email | `kontakt@hellion-media.de` |
|
||||
| Discord DM | `@j.j_kazama` |
|
||||
|
||||
Reports stay private. I will acknowledge within a few weekdays (European business hours) and tell you what I plan to do.
|
||||
Reports stay private. I will acknowledge within a few weekdays (European business hours) and tell
|
||||
you what I plan to do.
|
||||
|
||||
## Enforcement
|
||||
|
||||
I am the sole maintainer, so enforcement is a single-person process. I will pick the lightest measure that actually
|
||||
resolves the situation:
|
||||
I am the sole maintainer, so enforcement is a single-person process. I will pick the lightest
|
||||
measure that actually resolves the situation:
|
||||
|
||||
1. Private note asking the behaviour to stop.
|
||||
2. Public correction in the affected thread.
|
||||
@@ -95,16 +99,16 @@ Severe cases skip the lower steps. I will not negotiate over harassment or threa
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies to all spaces the project owns or that I run on its behalf: the GitHub repository, GitHub
|
||||
Discussions, project-related Discord conversations, and the maintainer contact listed in [`SECURITY.md`](SECURITY.md).
|
||||
It also applies when someone is identifiably representing HellionChat elsewhere, for example when posting as a
|
||||
HellionChat maintainer in the Dalamud Discord.
|
||||
This Code of Conduct applies to all spaces the project owns or that I run on its behalf: the GitHub
|
||||
repository, GitHub Discussions, project-related Discord conversations, and the maintainer contact
|
||||
listed in [`SECURITY.md`](SECURITY.md). It also applies when someone is identifiably representing
|
||||
HellionChat elsewhere, for example when posting as a HellionChat maintainer in the Dalamud Discord.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, available at
|
||||
[https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
||||
|
||||
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy
|
||||
of this license, visit
|
||||
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA
|
||||
4.0. To view a copy of this license, visit
|
||||
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
+70
-58
@@ -1,69 +1,75 @@
|
||||
# Contributing to HellionChat
|
||||
|
||||
Thanks for taking a look. HellionChat is a one-person side project developed under Hellion Forge. It started as a fork
|
||||
of [Chat 2](https://github.com/Infiziert90/ChatTwo) and has since become a standalone plugin under its own namespace,
|
||||
IPC channels and source tree (standalone-cut completed in v1.0.0). Forking HellionChat itself is explicitly permitted
|
||||
under the EUPL-1.2.
|
||||
Thanks for taking a look. HellionChat is a one-person side project developed under Hellion Forge. It
|
||||
started as a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) and has since become a
|
||||
standalone plugin under its own namespace, IPC channels and source tree (standalone-cut completed in
|
||||
v1.0.0). Forking HellionChat itself is explicitly permitted under the EUPL-1.2.
|
||||
|
||||
This document explains what I am looking for, what I am not, and how to make a contribution land smoothly.
|
||||
This document explains what I am looking for, what I am not, and how to make a contribution land
|
||||
smoothly.
|
||||
|
||||
## Before You Open Anything
|
||||
|
||||
- Read the [README](README.md) so you understand the scope: a privacy-focused, EUPL-1.2-licensed Dalamud plugin that
|
||||
intentionally removes the upstream webinterface and ships privacy-first defaults.
|
||||
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active cherry-picking from upstream Chat 2 has ended in the
|
||||
v1.4.x cycle; HellionChat continues as an independent codebase. Existing upstream-derived code keeps its attribution.
|
||||
New contributions stand on their own and do not need to be cherry-pick-compatible.
|
||||
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes through a private advisory, never a public issue
|
||||
or PR.
|
||||
- Read the [README](README.md) so you understand the scope: a privacy-focused, EUPL-1.2-licensed
|
||||
Dalamud plugin that intentionally removes the upstream webinterface and ships privacy-first
|
||||
defaults.
|
||||
- Read [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md). Active cherry-picking from upstream Chat 2
|
||||
has ended in the v1.4.x cycle; HellionChat continues as an independent codebase. Existing
|
||||
upstream-derived code keeps its attribution. New contributions stand on their own and do not need
|
||||
to be cherry-pick-compatible.
|
||||
- Read [`SECURITY.md`](SECURITY.md). Anything security-sensitive goes through a private advisory,
|
||||
never a public issue or PR.
|
||||
- Read the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## What I Will Accept
|
||||
|
||||
- Bug fixes for behaviour documented in the README, the in-plugin settings or the changelog.
|
||||
- Translation contributions for Hellion-specific strings via direct pull requests against
|
||||
`HellionChat/Resources/HellionStrings.*.resx`. Translations for upstream Chat 2 strings (`Language.*.resx`) are not
|
||||
handled here; those go to the upstream Chat 2 project.
|
||||
`HellionChat/Resources/HellionStrings.*.resx`. Translations for upstream Chat 2 strings
|
||||
(`Language.*.resx`) are not handled here; those go to the upstream Chat 2 project.
|
||||
- Documentation improvements (README, comments, this file).
|
||||
- Performance fixes with a measurable before/after.
|
||||
- New features that fit the privacy-first scope and do not duplicate what an existing Dalamud plugin already does well.
|
||||
- New features that fit the privacy-first scope and do not duplicate what an existing Dalamud plugin
|
||||
already does well.
|
||||
|
||||
## What I Will Probably Decline
|
||||
|
||||
- Re-introducing the webinterface or any remote-access feature. It was removed in v0.2.0 on purpose. See the README
|
||||
section "Was gegenüber Chat 2 fehlt".
|
||||
- Features that bypass the privacy filter or weaken the default retention behaviour without an explicit, documented
|
||||
opt-in.
|
||||
- Sweeping refactors that touch large parts of the codebase. The maintenance cost outweighs the benefit for a one-person
|
||||
project. (This used to be doubly important because of the upstream cherry-pick path; that path is closed now, but the
|
||||
rule still holds on its own merits.)
|
||||
- AI-generated code dropped in without disclosure or human review. See [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md)
|
||||
for how I handle AI assistance on my side; I expect comparable transparency from contributors.
|
||||
- Re-introducing the webinterface or any remote-access feature. It was removed in v0.2.0 on purpose.
|
||||
See the README section "Was gegenüber Chat 2 fehlt".
|
||||
- Features that bypass the privacy filter or weaken the default retention behaviour without an
|
||||
explicit, documented opt-in.
|
||||
- Sweeping refactors that touch large parts of the codebase. The maintenance cost outweighs the
|
||||
benefit for a one-person project. (This used to be doubly important because of the upstream
|
||||
cherry-pick path; that path is closed now, but the rule still holds on its own merits.)
|
||||
- AI-generated code dropped in without disclosure or human review. See
|
||||
[`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for how I handle AI assistance on my side; I
|
||||
expect comparable transparency from contributors.
|
||||
|
||||
If you are unsure whether an idea fits, open a feature-request issue first and ask before writing code. I would rather
|
||||
say "no" to a proposal than to a finished pull request.
|
||||
If you are unsure whether an idea fits, open a feature-request issue first and ask before writing
|
||||
code. I would rather say "no" to a proposal than to a finished pull request.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Open an issue (bug or feature request) using the templates under `.github/ISSUE_TEMPLATE/`. Skip this for trivial
|
||||
typos.
|
||||
2. Fork the repository and branch off `main`. Branch naming is informal; something like `fix/auto-tell-history-empty` or
|
||||
`feat/theme-export` is fine.
|
||||
3. Match the existing code style. The repository ships an `.editorconfig` that VS Code and Rider pick up automatically.
|
||||
4. Keep commits focused. Several small commits with clear messages are easier to review than one large one.
|
||||
Squash-on-merge happens at the PR level if needed.
|
||||
1. Open an issue (bug or feature request) using the templates under `.github/ISSUE_TEMPLATE/`. Skip
|
||||
this for trivial typos.
|
||||
2. Fork the repository and branch off `main`. Branch naming is informal; something like
|
||||
`fix/auto-tell-history-empty` or `feat/theme-export` is fine.
|
||||
3. Match the existing code style. The repository ships an `.editorconfig` that VS Code and Rider
|
||||
pick up automatically.
|
||||
4. Keep commits focused. Several small commits with clear messages are easier to review than one
|
||||
large one. Squash-on-merge happens at the PR level if needed.
|
||||
5. If your change touches user-visible behaviour, update the README and/or the changelog block in
|
||||
`HellionChat/HellionChat.yaml` and `repo.json`. I bump the version number myself at release time.
|
||||
6. Open the pull request against `main`. The PR template will ask you to summarise the change, the testing you did and
|
||||
any compatibility notes.
|
||||
6. Open the pull request against `main`. The PR template will ask you to summarise the change, the
|
||||
testing you did and any compatibility notes.
|
||||
|
||||
## Build and Test
|
||||
|
||||
The project targets `net10.0-windows` against Dalamud SDK 15. To build locally you need:
|
||||
|
||||
- .NET 10 SDK
|
||||
- A working Dalamud dev environment with `DALAMUD_HOME` set (XIVLauncher installed and launched once is the simplest
|
||||
path)
|
||||
- A working Dalamud dev environment with `DALAMUD_HOME` set (XIVLauncher installed and launched once
|
||||
is the simplest path)
|
||||
- VS Code with the C# Dev Kit, Rider, or Visual Studio
|
||||
|
||||
```bash
|
||||
@@ -71,11 +77,12 @@ dotnet restore
|
||||
dotnet build HellionChat.sln -c Release
|
||||
```
|
||||
|
||||
There are currently no tests in `HellionChat.sln`. If you add a test project, point it at the relevant subsystems
|
||||
(privacy filter, configuration migration, message store) and mention it in the PR.
|
||||
There are currently no tests in `HellionChat.sln`. If you add a test project, point it at the
|
||||
relevant subsystems (privacy filter, configuration migration, message store) and mention it in the
|
||||
PR.
|
||||
|
||||
For a smoke test in-game: build, copy the output into your Dalamud `devPlugins/HellionChat/` directory and load it via
|
||||
`/xlplugins`.
|
||||
For a smoke test in-game: build, copy the output into your Dalamud `devPlugins/HellionChat/`
|
||||
directory and load it via `/xlplugins`.
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
@@ -86,30 +93,33 @@ Every push and every pull request runs:
|
||||
| `build.yml` | `dotnet build` and `dotnet test` |
|
||||
| `codeql.yml` | CodeQL security analysis |
|
||||
|
||||
A pull request will not be merged while either of these is failing. CodeQL findings on changed code need to be
|
||||
addressed; pre-existing findings on untouched code are tracked separately.
|
||||
A pull request will not be merged while either of these is failing. CodeQL findings on changed code
|
||||
need to be addressed; pre-existing findings on untouched code are tracked separately.
|
||||
|
||||
## Translations
|
||||
|
||||
Hellion-specific strings live in `HellionChat/Resources/HellionStrings.resx` (English source) and
|
||||
`HellionStrings.<lang>.resx` (per-language). These are accepted as direct pull requests.
|
||||
|
||||
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx` are **not** translated here. They are kept as-is
|
||||
from the last upstream sync and remain the work of the Chat 2 Crowdin community. Active cherry-picking from upstream
|
||||
ended in the v1.4.x cycle (see [`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future translation improvements to
|
||||
those upstream strings will not flow into HellionChat automatically anymore. If you have improvements for the original
|
||||
Chat 2 strings, please contribute them to [Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) directly.
|
||||
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx` are **not** translated here.
|
||||
They are kept as-is from the last upstream sync and remain the work of the Chat 2 Crowdin community.
|
||||
Active cherry-picking from upstream ended in the v1.4.x cycle (see
|
||||
[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md)), so future translation improvements to those
|
||||
upstream strings will not flow into HellionChat automatically anymore. If you have improvements for
|
||||
the original Chat 2 strings, please contribute them to
|
||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) directly.
|
||||
|
||||
## Licensing
|
||||
|
||||
By submitting a pull request you confirm that:
|
||||
|
||||
- Your contribution is your own work, or you have the right to contribute it under the project licence.
|
||||
- You agree that your contribution will be released under the [EUPL-1.2](LICENSE), the same licence as the rest of the
|
||||
project.
|
||||
- Your contribution is your own work, or you have the right to contribute it under the project
|
||||
licence.
|
||||
- You agree that your contribution will be released under the [EUPL-1.2](LICENSE), the same licence
|
||||
as the rest of the project.
|
||||
|
||||
There is no separate CLA. Forking HellionChat is explicitly permitted under the EUPL-1.2, as with any EUPL-licensed
|
||||
project.
|
||||
There is no separate CLA. Forking HellionChat is explicitly permitted under the EUPL-1.2, as with
|
||||
any EUPL-licensed project.
|
||||
|
||||
## Response Times
|
||||
|
||||
@@ -119,8 +129,9 @@ project.
|
||||
| Discord DM | `@j.j_kazama` |
|
||||
| Email | `kontakt@hellion-media.de` |
|
||||
|
||||
I respond on weekdays during European business hours and take weekends and FFXIV patch days off. A pull request that
|
||||
sits for a few days has not been ignored. Pinging once after a week is fine; please do not ping daily.
|
||||
I respond on weekdays during European business hours and take weekends and FFXIV patch days off. A
|
||||
pull request that sits for a few days has not been ignored. Pinging once after a week is fine;
|
||||
please do not ping daily.
|
||||
|
||||
## First-time setup
|
||||
|
||||
@@ -130,9 +141,10 @@ After cloning, run once:
|
||||
./scripts/setup-hooks.sh
|
||||
```
|
||||
|
||||
This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight (versions/manifest/changelog/build).
|
||||
This wires `core.hooksPath` to `.githooks/`. The pre-push hook runs preflight
|
||||
(versions/manifest/changelog/build).
|
||||
|
||||
### Test suite
|
||||
|
||||
The plugin's test suite lives in a separate local repository and is not part of this codebase. If you need access for
|
||||
development, contact the maintainer.
|
||||
The plugin's test suite lives in a separate local repository and is not part of this codebase. If
|
||||
you need access for development, contact the maintainer.
|
||||
|
||||
@@ -9,6 +9,7 @@ using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
@@ -19,6 +20,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
private readonly Plugin _plugin;
|
||||
private readonly MessageManager _messageManager;
|
||||
private readonly MessageStore _store;
|
||||
private readonly ILogger<AutoTellTabsService> _logger;
|
||||
private readonly object _tempTabsLock = new();
|
||||
|
||||
// Hard cap on pinned TempTabs so the sidebar doesn't inflate over years
|
||||
@@ -29,11 +31,17 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
internal AutoTellTabsService(Plugin plugin, MessageManager messageManager, MessageStore store)
|
||||
internal AutoTellTabsService(
|
||||
Plugin plugin,
|
||||
MessageManager messageManager,
|
||||
MessageStore store,
|
||||
ILogger<AutoTellTabsService> logger
|
||||
)
|
||||
{
|
||||
_plugin = plugin;
|
||||
_messageManager = messageManager;
|
||||
_store = store;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// Derived from the tab list on read. Pin/Unpin/Promote/Logout simply
|
||||
@@ -67,7 +75,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
private void RehydratePinnedTabs()
|
||||
{
|
||||
var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool);
|
||||
Plugin.LogProxy.Debug($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found");
|
||||
_logger.LogDebug($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found");
|
||||
|
||||
foreach (var tab in Plugin.Config.Tabs)
|
||||
{
|
||||
@@ -76,7 +84,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
if (tab.TellTarget is null || !tab.TellTarget.IsSet())
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
$"[Pin] Pinned tab '{tab.Name}' has no usable TellTarget "
|
||||
+ $"(Name={tab.TellTarget?.Name ?? "<null>"} World={tab.TellTarget?.World ?? 0}). "
|
||||
+ "Chat input on this tab will be empty until the partner sends a tell or you /tell manually."
|
||||
@@ -93,7 +101,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
// sees the recent conversation, not a blank tab.
|
||||
PreloadHistory(tab, tab.TellTarget.Name, tab.TellTarget.World, Guid.Empty);
|
||||
|
||||
Plugin.LogProxy.Debug(
|
||||
_logger.LogDebug(
|
||||
$"[Pin] Rehydrated '{tab.Name}' -> Tell target {tab.TellTarget.Name}@{tab.TellTarget.World}"
|
||||
);
|
||||
}
|
||||
@@ -130,7 +138,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
if (partner == null)
|
||||
{
|
||||
// Diagnostics: helps detect regressions (FFXIV payload changes, new edge cases)
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, "
|
||||
+ $"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, "
|
||||
+ $"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, "
|
||||
@@ -361,7 +369,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Non-fatal: tab still spawns with visible error notice instead of silent history loss
|
||||
Plugin.LogProxy.Error(ex, "[AutoTellTabs] History preload failed");
|
||||
_logger.LogError(ex, "[AutoTellTabs] History preload failed");
|
||||
tab.Messages.AddPrune(
|
||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
|
||||
MessageManager.MessageDisplayLimit
|
||||
@@ -456,7 +464,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
if (!tab.IsTempTab || tab.IsPinned)
|
||||
{
|
||||
Plugin.LogProxy.Debug(
|
||||
_logger.LogDebug(
|
||||
$"[Pin] TryPin skipped: IsTempTab={tab.IsTempTab} IsPinned={tab.IsPinned}"
|
||||
);
|
||||
return false;
|
||||
@@ -472,7 +480,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
|
||||
tab.IsPinned = true;
|
||||
Plugin.LogProxy.Debug(
|
||||
_logger.LogDebug(
|
||||
$"[Pin] Pinned tab '{tab.Name}' target={tab.TellTarget?.Name}@{tab.TellTarget?.World}"
|
||||
);
|
||||
_plugin.SaveConfig();
|
||||
@@ -495,7 +503,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
|
||||
tab.IsPinned = false;
|
||||
Plugin.LogProxy.Debug("[Pin] Unpinned tab '{tab.Name}'");
|
||||
_logger.LogDebug("[Pin] Unpinned tab '{TabName}'", tab.Name);
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
@@ -509,9 +517,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
tab.IsTempTab = false;
|
||||
tab.IsPinned = false;
|
||||
tab.TellTarget = TellTarget.Empty();
|
||||
Plugin.LogProxy.Debug(
|
||||
$"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)"
|
||||
);
|
||||
_logger.LogDebug($"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)");
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace HellionChat.Branding;
|
||||
|
||||
// Lazy-loaded provenance art that ships embedded with the DLL. Two
|
||||
// variants:
|
||||
//
|
||||
// - FoxBanner: the full-size silhouette with "Hellion Forge" inside
|
||||
// the body — rendered in the first-run wizard and the Information
|
||||
// tab as a small "about the makers" anchor.
|
||||
// - FoxMini: the four-line fox-head + curly-tail that gets stitched
|
||||
// into the DI-logger bootstrap line so an xllog reader sees the
|
||||
// same signature on every plugin load.
|
||||
//
|
||||
// Both files live as embedded resources under HellionChat.Branding.* so
|
||||
// the plugin DLL is self-contained — no on-disk asset lookup that could
|
||||
// silently miss after a partial deploy.
|
||||
internal static class HellionForgeAscii
|
||||
{
|
||||
private static string? _foxBanner;
|
||||
private static string? _foxMini;
|
||||
|
||||
public static string FoxBanner => _foxBanner ??= Load("HellionChat.Branding.fox-banner.txt");
|
||||
|
||||
public static string FoxMini => _foxMini ??= Load("HellionChat.Branding.fox-mini.txt");
|
||||
|
||||
private static string Load(string resourceName)
|
||||
{
|
||||
using var stream = typeof(HellionForgeAscii).Assembly.GetManifestResourceStream(
|
||||
resourceName
|
||||
);
|
||||
if (stream is null)
|
||||
return string.Empty;
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
using Dalamud.Game.Command;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
internal sealed class Commands : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, CommandWrapper> Registered = [];
|
||||
private readonly ILogger<Commands> _logger;
|
||||
|
||||
public Commands(ILogger<Commands> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@@ -52,7 +59,7 @@ internal sealed class Commands : IDisposable
|
||||
{
|
||||
if (!Registered.TryGetValue(command, out var wrapper))
|
||||
{
|
||||
Plugin.LogProxy.Warning($"Missing registration for command {command}");
|
||||
_logger.LogWarning($"Missing registration for command {command}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +69,7 @@ internal sealed class Commands : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, $"Error while executing command {command}");
|
||||
_logger.LogError(ex, $"Error while executing command {command}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ public class Configuration : IPluginConfiguration
|
||||
public bool SortAutoTranslate;
|
||||
public bool CollapseDuplicateMessages;
|
||||
public bool CollapseKeepUniqueLinks;
|
||||
public bool SymbolPickerEnabled = true;
|
||||
public bool PlaySounds = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public int MaxLinesToRender = 2_500; // 1-10000
|
||||
@@ -270,6 +271,7 @@ public class Configuration : IPluginConfiguration
|
||||
SortAutoTranslate = other.SortAutoTranslate;
|
||||
CollapseDuplicateMessages = other.CollapseDuplicateMessages;
|
||||
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
|
||||
SymbolPickerEnabled = other.SymbolPickerEnabled;
|
||||
PlaySounds = other.PlaySounds;
|
||||
KeepInputFocus = other.KeepInputFocus;
|
||||
MaxLinesToRender = other.MaxLinesToRender;
|
||||
|
||||
+152
-109
@@ -1,23 +1,44 @@
|
||||
using Dalamud;
|
||||
using Dalamud;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
public class FontManager
|
||||
// Two LogProxy sites live in static methods (TryGetHellionFontBytes,
|
||||
// AddFontWithFallback); a ctor-injected ILogger would not be reachable
|
||||
// from those scopes, so the class stays on Plugin.LogProxy.
|
||||
//
|
||||
// Hybrid handle model: Axis and AxisItalic mirror the game's current
|
||||
// font state and are init-only. FontAwesome reuses Dalamud's UiBuilder
|
||||
// fixed-width icon handle and is likewise init-only. RegularFont and
|
||||
// ItalicFont depend on user-toggleable settings and get replaced live
|
||||
// via RebuildDelegateFonts when those settings change; they stay as
|
||||
// mutable nullable fields.
|
||||
//
|
||||
// The four atlas-owned handles register inside a single
|
||||
// SuppressAutoRebuild block so the font atlas only rebuilds once for the
|
||||
// whole plugin start instead of once per handle. FontAwesome lives
|
||||
// outside that accounting because the UiBuilder already owns it.
|
||||
public sealed class FontManager : IDisposable
|
||||
{
|
||||
internal IFontHandle Axis = null!;
|
||||
internal IFontHandle AxisItalic = null!;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
|
||||
internal IFontHandle RegularFont = null!;
|
||||
internal IFontHandle Axis { get; init; }
|
||||
internal IFontHandle AxisItalic { get; init; }
|
||||
internal IFontHandle FontAwesome { get; init; }
|
||||
|
||||
// Mutable because the live font settings replace these via
|
||||
// RebuildDelegateFonts. Reference replacement is atomic for reference
|
||||
// types, so push sites that read the field once per frame see at most
|
||||
// one stale handle.
|
||||
internal IFontHandle? RegularFont;
|
||||
internal IFontHandle? ItalicFont;
|
||||
|
||||
internal IFontHandle FontAwesome = null!;
|
||||
|
||||
private ushort[] Ranges = [];
|
||||
private ushort[] JpRange = [];
|
||||
|
||||
@@ -44,10 +65,128 @@ public class FontManager
|
||||
// Hellion font bytes (Exo 2, OFL-1.1); lazily loaded from manifest resources
|
||||
private static byte[]? HellionFontBytes;
|
||||
|
||||
// Returns null when the embedded font resource is missing. Should never
|
||||
public FontManager(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
_pluginInterface = pluginInterface;
|
||||
SetUpRanges();
|
||||
|
||||
var atlas = _pluginInterface.UiBuilder.FontAtlas;
|
||||
|
||||
using (atlas.SuppressAutoRebuild())
|
||||
{
|
||||
Axis = atlas.NewGameFontHandle(
|
||||
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
|
||||
);
|
||||
|
||||
AxisItalic = atlas.NewGameFontHandle(
|
||||
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
|
||||
{
|
||||
SkewStrength = SizeInPx(Plugin.Config.FontSizeV2) / 6,
|
||||
}
|
||||
);
|
||||
|
||||
FontAwesome = _pluginInterface.UiBuilder.IconFontFixedWidthHandle;
|
||||
|
||||
RegularFont = BuildRegularFontHandle(atlas);
|
||||
|
||||
if (Plugin.Config.ItalicEnabled)
|
||||
ItalicFont = BuildItalicFontHandle(atlas);
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the settings save path when one of the font-related
|
||||
// settings changed. Game fonts and FontAwesome stay untouched because
|
||||
// none of those settings affect them.
|
||||
//
|
||||
// Thread model: the settings save path runs on the ImGui draw thread,
|
||||
// same as every push site. The rebuild finishes synchronously before
|
||||
// the next push reads the field in the same frame, so there is no
|
||||
// cross-thread race on the handle reference.
|
||||
public void RebuildDelegateFonts()
|
||||
{
|
||||
SetUpRanges();
|
||||
|
||||
var atlas = _pluginInterface.UiBuilder.FontAtlas;
|
||||
|
||||
RegularFont?.Dispose();
|
||||
RegularFont = BuildRegularFontHandle(atlas);
|
||||
|
||||
ItalicFont?.Dispose();
|
||||
ItalicFont = Plugin.Config.ItalicEnabled ? BuildItalicFontHandle(atlas) : null;
|
||||
}
|
||||
|
||||
// Instance method so Ranges / JpRange are reachable without parameter
|
||||
// plumbing; PascalCase field names follow the existing class style.
|
||||
private IFontHandle BuildRegularFontHandle(IFontAtlas atlas) =>
|
||||
atlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
// UseHellionFont swaps the source font but keeps the size
|
||||
// selector tied to FontSizeV2 (the Hellion font ships as
|
||||
// a single weight).
|
||||
var basePt = Plugin.Config.UseHellionFont
|
||||
? Plugin.Config.FontSizeV2
|
||||
: Plugin.Config.GlobalFontV2.SizePt;
|
||||
var config = new SafeFontConfig { SizePt = basePt, GlyphRanges = Ranges };
|
||||
// Missing embedded resource falls back to the configured
|
||||
// system font instead of taking the whole UiBuilder down.
|
||||
var hellionBytes = Plugin.Config.UseHellionFont ? TryGetHellionFontBytes() : null;
|
||||
config.MergeFont = hellionBytes is not null
|
||||
? tk.AddFontFromMemory(hellionBytes, config, "Hellion-Exo2")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
);
|
||||
|
||||
private IFontHandle BuildItalicFontHandle(IFontAtlas atlas) =>
|
||||
atlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
var config = new SafeFontConfig
|
||||
{
|
||||
SizePt = Plugin.Config.ItalicFontV2.SizePt,
|
||||
GlyphRanges = Ranges,
|
||||
};
|
||||
config.MergeFont = AddFontWithFallback(
|
||||
tk,
|
||||
Plugin.Config.ItalicFontV2.FontId,
|
||||
config,
|
||||
"italic"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Axis.Dispose();
|
||||
AxisItalic.Dispose();
|
||||
// FontAwesome is shared with the UiBuilder; the host owns its
|
||||
// lifetime, so the plugin must not dispose it.
|
||||
RegularFont?.Dispose();
|
||||
ItalicFont?.Dispose();
|
||||
}
|
||||
|
||||
// Returns null when the embedded font resource is missing. Should not
|
||||
// happen on a signed release build, but a broken csproj or hand-rolled
|
||||
// dev build can land here. Caller falls back to the system font path so
|
||||
// the plugin still loads instead of crashing the whole UiBuilder.
|
||||
// dev build can land here. Caller falls back to the system font path
|
||||
// so the plugin still loads instead of crashing the whole UiBuilder.
|
||||
private static byte[]? TryGetHellionFontBytes()
|
||||
{
|
||||
if (HellionFontBytes is not null)
|
||||
@@ -95,10 +234,8 @@ public class FontManager
|
||||
foreach (var c in reader.Glyphs)
|
||||
builder.AddChar(c.Char);
|
||||
|
||||
// various symbols
|
||||
// French
|
||||
// Romanian
|
||||
// builder.AddText("←→↑↓《》■※☀★★☆♥♡ヅツッシ☀☁☂℃℉°♀♂♠♣♦♣♧®©™€$£♯♭♪✓√◎◆◇♦■□〇●△▽▼▲‹›≤≥<«“”─\~");
|
||||
builder.AddText("Œœ");
|
||||
builder.AddText("ĂăÂâÎîȘșȚț");
|
||||
|
||||
@@ -119,100 +256,6 @@ public class FontManager
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
||||
}
|
||||
|
||||
// CPU-bound build offloaded to Task.Run; runs parallel with theme init
|
||||
public async Task BuildFontsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await Task.Run(BuildFonts, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void BuildFonts()
|
||||
{
|
||||
SetUpRanges();
|
||||
|
||||
Axis = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(
|
||||
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
|
||||
);
|
||||
AxisItalic = Plugin.Interface.UiBuilder.FontAtlas.NewGameFontHandle(
|
||||
new GameFontStyle(GameFontFamily.Axis, SizeInPx(Plugin.Config.FontSizeV2))
|
||||
{
|
||||
SkewStrength = SizeInPx(Plugin.Config.FontSizeV2) / 6,
|
||||
}
|
||||
);
|
||||
|
||||
FontAwesome = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
{
|
||||
e.OnPreBuild(tk =>
|
||||
tk.AddFontAwesomeIconFont(new SafeFontConfig { SizePx = GetFontSize() })
|
||||
);
|
||||
e.OnPostBuild(tk => tk.FitRatio(tk.Font));
|
||||
});
|
||||
|
||||
RegularFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
// v1.2.0: UseHellionFont controls font size selection
|
||||
var basePt = Plugin.Config.UseHellionFont
|
||||
? Plugin.Config.FontSizeV2
|
||||
: Plugin.Config.GlobalFontV2.SizePt;
|
||||
var config = new SafeFontConfig { SizePt = basePt, GlyphRanges = Ranges };
|
||||
// F10.2: if the embedded font is missing, drop to the system font
|
||||
// path rather than letting the UiBuilder throw.
|
||||
var hellionBytes = Plugin.Config.UseHellionFont ? TryGetHellionFontBytes() : null;
|
||||
config.MergeFont = hellionBytes is not null
|
||||
? tk.AddFontFromMemory(hellionBytes, config, "Hellion-Exo2")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
);
|
||||
|
||||
if (Plugin.Config.ItalicEnabled)
|
||||
{
|
||||
ItalicFont = Plugin.Interface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
var config = new SafeFontConfig
|
||||
{
|
||||
SizePt = Plugin.Config.ItalicFontV2.SizePt,
|
||||
GlyphRanges = Ranges,
|
||||
};
|
||||
config.MergeFont = AddFontWithFallback(
|
||||
tk,
|
||||
Plugin.Config.ItalicFontV2.FontId,
|
||||
config,
|
||||
"italic"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
Plugin.Config.JapaneseFontV2.FontId,
|
||||
config,
|
||||
"japanese"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
tk.Font = config.MergeFont;
|
||||
})
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItalicFont = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Add font with fallback to NotoSansCjkRegular if unavailable
|
||||
private static ImFontPtr AddFontWithFallback(
|
||||
IFontAtlasBuildToolkitPreBuild tk,
|
||||
@@ -234,9 +277,9 @@ public class FontManager
|
||||
or ArgumentException
|
||||
)
|
||||
{
|
||||
// Atlas-toolkit throws span IO and validation failures; routing the
|
||||
// wider set through the fallback keeps a corrupt font config from
|
||||
// taking down the whole atlas build.
|
||||
// Atlas-toolkit throws span IO and validation failures; routing
|
||||
// the wider set through the fallback keeps a corrupt font config
|
||||
// from taking down the whole atlas build.
|
||||
Plugin.LogProxy.Warning(
|
||||
e,
|
||||
$"Configured {slot} font failed to load ({e.GetType().Name}), "
|
||||
|
||||
@@ -19,6 +19,7 @@ using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using InteropGenerator.Runtime;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
|
||||
|
||||
namespace HellionChat.GameFunctions;
|
||||
@@ -98,9 +99,12 @@ internal sealed unsafe class Chat : IDisposable
|
||||
private long LastPlayerNameDisplayTypeRefresh;
|
||||
private PlayerNameDisplayType CurrentPlayerNameDisplayType = PlayerNameDisplayType.FullName;
|
||||
|
||||
public Chat(Plugin plugin)
|
||||
private readonly ILogger<Chat> _logger;
|
||||
|
||||
public Chat(Plugin plugin, ILogger<Chat> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
ChatLogRefreshHook?.Enable();
|
||||
@@ -236,7 +240,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in chat Activated event");
|
||||
_logger.LogError(ex, "Error in chat Activated event");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -266,7 +270,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in chat Activated event");
|
||||
_logger.LogError(ex, "Error in chat Activated event");
|
||||
}
|
||||
|
||||
return 1; // Prevent vanilla chat log from gaining focus
|
||||
@@ -299,7 +303,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
{
|
||||
playerName = SeString.Parse(agent->TellPlayerName).TextValue;
|
||||
worldId = agent->TellWorldId;
|
||||
Plugin.LogProxy.Debug($"Detected tell target '[redacted]'@{worldId}");
|
||||
_logger.LogDebug($"Detected tell target '[redacted]'@{worldId}");
|
||||
}
|
||||
|
||||
Plugin.CurrentTab.CurrentChannel = new UsedChannel
|
||||
@@ -358,7 +362,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in chat Activated event");
|
||||
_logger.LogError(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +412,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in chat Activated event");
|
||||
_logger.LogError(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +628,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
if (contentId == 0)
|
||||
{
|
||||
Plugin.ChatGui.PrintError(Language.Chat_SendTell_Error);
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
"Tried to send a tell with ContentId being 0, sorry this is an internal error."
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,7 @@ using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
|
||||
|
||||
namespace HellionChat.GameFunctions;
|
||||
@@ -37,14 +38,20 @@ internal unsafe class GameFunctions : IDisposable
|
||||
#endregion
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
private readonly ILogger<GameFunctions> _logger;
|
||||
internal KeybindManager KeybindManager { get; }
|
||||
internal Chat Chat { get; }
|
||||
|
||||
internal GameFunctions(Plugin plugin)
|
||||
internal GameFunctions(
|
||||
Plugin plugin,
|
||||
ILogger<GameFunctions> logger,
|
||||
ILoggerFactory loggerFactory
|
||||
)
|
||||
{
|
||||
Plugin = plugin;
|
||||
KeybindManager = new KeybindManager(plugin);
|
||||
Chat = new Chat(Plugin);
|
||||
_logger = logger;
|
||||
KeybindManager = new KeybindManager(plugin, loggerFactory.CreateLogger<KeybindManager>());
|
||||
Chat = new Chat(Plugin, loggerFactory.CreateLogger<Chat>());
|
||||
|
||||
Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
ResolveTextCommandPlaceholderHook?.Enable();
|
||||
@@ -215,6 +222,7 @@ internal unsafe class GameFunctions : IDisposable
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Static method, no instance _logger reachable here.
|
||||
Plugin.LogProxy.Warning(e, "Unable to open adventurer plate");
|
||||
return false;
|
||||
}
|
||||
@@ -255,7 +263,7 @@ internal unsafe class GameFunctions : IDisposable
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(ReplacementName);
|
||||
if (byteCount >= PlaceholderBufferSize)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
$"Replacement name too long for placeholder buffer ({byteCount} bytes >= {PlaceholderBufferSize}); falling back to original."
|
||||
);
|
||||
ReplacementName = null;
|
||||
|
||||
@@ -8,6 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModifierFlag = HellionChat.GameFunctions.Types.ModifierFlag;
|
||||
|
||||
namespace HellionChat.GameFunctions;
|
||||
@@ -306,9 +307,12 @@ internal unsafe class KeybindManager : IDisposable
|
||||
// VirtualKey.OEM_CLEAR,
|
||||
};
|
||||
|
||||
internal KeybindManager(Plugin plugin)
|
||||
private readonly ILogger<KeybindManager> _logger;
|
||||
|
||||
internal KeybindManager(Plugin plugin, ILogger<KeybindManager> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
// Handle keybinds from the game on every tick.
|
||||
@@ -507,7 +511,7 @@ internal unsafe class KeybindManager : IDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in chat Activated event");
|
||||
_logger.LogError(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
|
||||
<Version>1.4.8</Version>
|
||||
<Version>1.5.1</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Use lock file to pin exact versions -->
|
||||
@@ -15,6 +15,14 @@
|
||||
<!-- Closed ranges prevent surprise major bumps during lock file regeneration -->
|
||||
<PackageReference Include="MessagePack" Version="[3.1.4, 4.0.0)" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.7" />
|
||||
<!-- v1.5.0 DI-container foundation; matches Lightless pin (Hosting 10.0.7) -->
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.DependencyInjection"
|
||||
Version="[10.0.7, 11.0.0)"
|
||||
/>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="[10.0.7, 11.0.0)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="[10.0.7, 11.0.0)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="[10.0.7, 11.0.0)" />
|
||||
<!-- SQLitePCLRaw override for CVE-2025-6965, CVE-2025-7709 (SQLite >= 3.50.3) -->
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
|
||||
<PackageReference Include="morelinq" Version="4.4.0" />
|
||||
@@ -50,14 +58,21 @@
|
||||
<EmbeddedResource Include="Resources\HellionFont-OFL.txt">
|
||||
<LogicalName>HellionFont-OFL.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-banner.txt">
|
||||
<LogicalName>HellionChat.Branding.fox-banner.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-mini.txt">
|
||||
<LogicalName>HellionChat.Branding.fox-mini.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Themes\Builtin\example-theme.json">
|
||||
<LogicalName>HellionChat.Themes.Builtin.example-theme.json</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Plugin icon: copy images/* to output for Dalamud discovery -->
|
||||
<!-- Plugin icon: copy images/* to output for Dalamud discovery. ASCII
|
||||
study folder is source-only material, no need to ship it. -->
|
||||
<ItemGroup>
|
||||
<None Include="images\**">
|
||||
<None Include="images\**" Exclude="images\ascii\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
+154
-113
@@ -35,142 +35,183 @@ tags:
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**v1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)**
|
||||
**v1.5.1 — FontAtlas Refactor and Hellion Forge Signature (2026-05-17)**
|
||||
|
||||
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer
|
||||
cluster (DbViewer FTS5 full-text search, ad-block foundation
|
||||
investigation) plus three polish quick-wins.
|
||||
Hybrid FontManager refactor plus an embedded provenance mark.
|
||||
|
||||
- DbViewer full-text search: optional FTS5 index across the full
|
||||
chat history. Built asynchronously on first load after the
|
||||
update with a progress toast. The local page-filter remains
|
||||
available as the default mode. Queries match as exact phrases
|
||||
-- multi-word terms must appear together in order; advanced
|
||||
users can opt into raw FTS5 MATCH syntax by wrapping their own
|
||||
double-quotes.
|
||||
- Custom theme files now auto-reload when edited while the theme
|
||||
is active -- no need to re-click the theme in the picker.
|
||||
- Retention sweep no longer blocks the framework thread, removing
|
||||
the ~194ms mini-hitch per sweep.
|
||||
- Status bar renders correctly at Windows display scaling > 100%.
|
||||
- Receive-suppressed-tells routing investigated this cycle and
|
||||
postponed to v1.5.x: when other plugins suppress tells via
|
||||
CheckMessageHandled, the FFXIV chat pipeline skips the
|
||||
RaptureLogModule.AddMsgSourceEntry path so HellionChat's
|
||||
ContentIdResolverHook does not fire and tell-partner
|
||||
identification breaks. The fix belongs next to the planned
|
||||
ad-block hook layer where the same patch surface comes up.
|
||||
- Internal: messages.Id is declared BLOB but stored as TEXT
|
||||
(Microsoft.Data.Sqlite Guid binding). FTS bulk insert and
|
||||
LoadByGuids match the TEXT storage form on both sides.
|
||||
Migration v17 stays (no schema bump).
|
||||
What changes under the hood:
|
||||
|
||||
- FontManager handle creation moves into the ctor inside a single
|
||||
atlas.SuppressAutoRebuild() block. The font atlas now builds once
|
||||
per plugin load instead of four to five times — less CPU and GPU
|
||||
pressure in the first seconds after a reload, less atlas texture
|
||||
memory churn.
|
||||
- Hybrid property model: Axis, AxisItalic and FontAwesome become
|
||||
init-only handles. RegularFont and ItalicFont stay mutable because
|
||||
the eight font settings still need to replace them at runtime —
|
||||
that path is funnelled through RebuildDelegateFonts() now and
|
||||
runs without a plugin reload.
|
||||
- FontAwesome reuses Dalamud's UiBuilder.IconFontFixedWidthHandle
|
||||
instead of building its own atlas slot. One delegate-build step
|
||||
less in the ctor.
|
||||
- BuildFontsAsync and BuildFonts are removed; the live mutation
|
||||
path is RebuildDelegateFonts() now.
|
||||
- Two FontManager self-test steps registered with /xlperf: ctor
|
||||
smoke (every handle non-null after Phase-1 resolve, no atlas
|
||||
load-exception) and push smoke (Push() returns without throwing).
|
||||
|
||||
Honorific full-gradient port (originally the v1.5.1 main item) was
|
||||
dropped: Honorific 3.2 exposes no IPC for the rendered gradient
|
||||
frame, and an in-plugin port of the colour palette was declined.
|
||||
The integration stays at the v1.4.7 glow-only shape.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Hellion Forge signature: a small fox-head ASCII silhouette is
|
||||
emitted to /xllog on every plugin load, and a full fox banner
|
||||
with "Hellion Forge" set inside the body is available as a
|
||||
folded TreeNode in the First-Run Wizard and Settings ->
|
||||
Information tab. Drawn by Julia Moon, embedded in the plugin DLL.
|
||||
- No settings changes, no migration. v17 stays.
|
||||
|
||||
Note on performance: the cross-plugin baseline target from v1.5.0
|
||||
(matching Lightless and XIVInstantMessenger at ~7 ms HITCH) did
|
||||
not land this cycle. HITCH stays around 80 ms because the cost is
|
||||
in the UiBuilder first-frame render path, not in the atlas build
|
||||
(which this cycle did reduce from 4-5 builds per load to 1). A
|
||||
first-frame render investigation is reserved for a later cycle.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.4.7 — Backlog Cleanup and Mid-Features (2026-05-13)**
|
||||
**v1.5.0 — DI Foundation and Service Refactor (2026-05-17)**
|
||||
|
||||
Eighth sub-patch of the v1.4.x polish-sweep series. First
|
||||
user-visible feature bundle since v1.4.5 — pinned tell tabs that
|
||||
survive relog, opt-in Honorific glow rendering, and a configurable
|
||||
sidebar.
|
||||
Major architecture cycle. The plugin bootstrap moves to a
|
||||
generic-host DI container (Microsoft.Extensions.Hosting +
|
||||
IServiceCollection) modelled on Lightless Sync. Service logging
|
||||
moves from a static Plugin.LogProxy locator to typed
|
||||
Microsoft.Extensions.Logging.ILogger<T> via constructor injection,
|
||||
bridged over Dalamud's IPluginLog by a custom DalamudLogger trio.
|
||||
|
||||
- TempTell Pin: right-click a TempTell tab in the sidebar to pin
|
||||
it. Pinned tabs survive relog, keep their conversation history
|
||||
(loaded on demand from the message store), and stay bound to
|
||||
the same /tell partner. Hard cap of 5 pinned tabs in a pool
|
||||
separate from the 15-tab auto-tell pool — total ceiling is 20
|
||||
tabs. New 'Pinned' section in the sidebar with its own divider
|
||||
header
|
||||
- Honorific Glow outline now renders when the title carries a
|
||||
Glow colour. Opt-in via Settings → Integrations → 'Render glow
|
||||
outlines (Honorific)' (default off, dodges the per-frame
|
||||
DrawList overhead on low-end hardware). Gradient (Color3 /
|
||||
GradientColourSet / Wave / Pulse) is parsed but rendered
|
||||
statically — a later cycle will port the full animation
|
||||
- Sidebar width is now configurable in Theme & Layout (range
|
||||
44–160 px). Default stays icon-only; widen to fit section
|
||||
headers like 'Active Tells (3)' without truncation
|
||||
- Settings Save no longer pops the chat input back to /tell with
|
||||
a pinned partner — Configuration.UpdateFrom now preserves the
|
||||
runtime CurrentChannel across the persistent-tab merge, and
|
||||
TabSwitched deep-clones the seeded channel instead of sharing
|
||||
the previous tab's UsedChannel
|
||||
- Util/ImGuiUtil.cs DrawArrows IconButton id now uses
|
||||
(id + 1).ToString() instead of the operator-precedence quirk
|
||||
id + 1.ToString() — generated IDs stay numerically stable
|
||||
- Internal: IPluginLogProxy indirection over Dalamud's IPluginLog
|
||||
routes all ~91 Plugin.Log call sites through a testable proxy.
|
||||
MessageStore.Migrate0 can now run in xUnit without loading
|
||||
Dalamud.dll, closing the gap F12.1 left in v1.4.6
|
||||
- Internal: TempTab counter switched from an Interlocked cached
|
||||
field to a derived Tabs.Count(predicate) — pin-state transitions
|
||||
are cold-path and don't need lock-free reads
|
||||
What changes under the hood:
|
||||
|
||||
- 18 instance-class services migrate to ILogger<T> via constructor
|
||||
injection across four slices: data layer (MessageStore,
|
||||
MessageManager, AutoTellTabsService), IPC and integrations
|
||||
(HonorificService, IpcManager, TypingIpc, ExtraChat, the three
|
||||
GameFunctions classes), UI window layer (ChatLogWindow,
|
||||
DbViewer, Popout, three settings tabs), and root (Commands,
|
||||
ThemeRegistry, PayloadHandler).
|
||||
- Plugin.LogProxy stays in place for the eight buckets ctor
|
||||
injection cannot reach: static helpers (EmoteCache,
|
||||
AutoTranslate, MemoryUtil, WrapperUtil), Dalamud-reflected
|
||||
types (Configuration), the Message data class, and instance
|
||||
classes that only log from static methods (FontManager, one
|
||||
GameFunctions site).
|
||||
- Plugin.cs finishes at 1012 lines — virtually identical to the
|
||||
pre-cycle 1013. The new Phase-1 host build and Plugin.X bridge
|
||||
wiring trade out exactly the service and window allocations
|
||||
that previously lived in LoadAsync.
|
||||
- Cross-plugin baseline confirms no performance penalty against
|
||||
Chat 2: HellionChat first-frame HITCH 77 ms median, Chat 2
|
||||
74 ms median. Lightless and XIVInstantMessenger sit around
|
||||
7 ms by deferring their font-atlas build past Finished
|
||||
loading — that pattern is the v1.5.1 follow-up.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Slash-command insert fix: pasting a slash command into the
|
||||
chat input (Friend List "/tell" action, plugin-driven inserts
|
||||
from Artisan, AllaganTools etc.) now replaces the existing
|
||||
input instead of concatenating. Cherry-picked from ChatTwo
|
||||
upstream ee7768ac with namespace adaptation.
|
||||
|
||||
Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.4.6 — Code Hygiene and Refactor (2026-05-12)**
|
||||
**v1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)**
|
||||
|
||||
Maintenance patch. No user-visible behaviour changes; tightens the
|
||||
development feedback loop, fixes two upstream-inherited bugs, and
|
||||
prepares the code for the v1.4.7 backlog cleanup.
|
||||
Eleventh and final sub-patch of the v1.4.x polish-sweep series.
|
||||
Symbol picker for the chat input, a tell-history reload fix for
|
||||
users with many active partners, and a closing cleanup sweep
|
||||
before v1.5.0 picks up the DI-container adoption.
|
||||
|
||||
- preflight.sh gains a csharpier reflow check and a markdownlint
|
||||
pass so style drift and markdown violations are caught at the
|
||||
pre-push gate
|
||||
- FontManager fallback catches the full set of atlas-toolkit
|
||||
throws (IO, InvalidOperation, ArgumentException) — a corrupt
|
||||
font config no longer takes down the whole atlas build
|
||||
- BrandingLinks and IntegrationLinks URLs validated on plugin
|
||||
load — a typo in a future URL rotation now throws at startup
|
||||
- Cherry-picked from ChatTwo upstream f35b7d3: Chat.SetChannel
|
||||
no longer leaks the native Utf8String when the linkshell check
|
||||
rejects the channel
|
||||
- Cherry-picked from ChatTwo upstream f35b7d3: Tab.Clone now
|
||||
deep-clones UsedChannel and TellTarget — PopOut and Temp tabs
|
||||
no longer mutate each other's channel state
|
||||
- Active-tab underline scales with DPI and rounds to physical
|
||||
pixels for crisp rendering above 100% scaling
|
||||
- IconButton width parameter no longer subtracts HUD-scaled
|
||||
padding from a raw int (measured width passes through verbatim)
|
||||
- Internal: HellionStyle ChildBgAlpha extracted to a testable
|
||||
helper; Plugin.SaveConfig clones only the temp tabs;
|
||||
SettingsOverview caches the draw-list per frame;
|
||||
Dalamud.Utility.Util surface routed through an IPlatformUtil
|
||||
indirection (MessageStore IsWine probe is now testable in
|
||||
isolation)
|
||||
- Built-in themes: Crystal Nocturne (sapphire and electric
|
||||
magenta over obsidian, by CRYSTALLITE) replaces Moonlit Bloom.
|
||||
Users with Moonlit Bloom selected fall back to Hellion Arctic
|
||||
on first load
|
||||
- Symbol picker: a small smile-icon button left of the channel
|
||||
indicator opens a popup with two tabs. The first lists all 161
|
||||
FFXIV PUA glyphs (Dalamud's SeIconChar enum); the second
|
||||
carries 97 server-verified BMP symbols (latin marks, currency,
|
||||
the full Greek alphabet, geometric shapes, suits, notes) —
|
||||
every one of them round-tripped through /echo and /say in a
|
||||
four-round probe so the in-channel render matches what the
|
||||
picker shows. Click drops the glyph at the caret, multi-insert
|
||||
keeps the popup open, and a recent-used strip floats the last
|
||||
sixteen picks across both tabs. Toggle in Settings → Chat →
|
||||
Message behaviour, default on.
|
||||
- Pinned auto-tell tabs reload their full history again: a
|
||||
hidden 500-row scan cap in PreloadHistory used to override the
|
||||
user-configurable AutoTellTabsHistoryPreload setting, so
|
||||
less-frequent pinned partners (rare /tell sessions in an
|
||||
otherwise busy week) lost their backlog. The cap is removed;
|
||||
the (Receiver, Date) index keeps SQL fast, the client-side
|
||||
loop still respects your setting as the upper bound.
|
||||
- Slash-command teardown: /hellion, /hellionView,
|
||||
/hellionDebugger (and #if DEBUG /hellionSeString) wrappers are
|
||||
now cached as private fields. Plugin teardown detaches the
|
||||
live registration instead of re-Register'ing with identical
|
||||
args — closes a latent maintenance hazard from v1.4.9.
|
||||
- v1.4.x polish-sweep wraps up here. The ImGuiListClipper render
|
||||
refactor that was on the v1.4.10 reserve list got dropped
|
||||
after cross-platform smoke showed the scroll rubber-band is a
|
||||
Wine / Linux render-pipeline quirk, not universal — Windows
|
||||
users never saw it. It will get its own platform-targeted
|
||||
spike in a later patch. Next major cycle is v1.5.0 with the
|
||||
DI-container adoption (Microsoft.Extensions.Hosting +
|
||||
ILogger<T>) modelled on Lightless.
|
||||
- Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.4.5 — UX and Robustness (2026-05-12)**
|
||||
**v1.4.9 — Plugin-Load Render Polish (2026-05-15)**
|
||||
|
||||
Sixth sub-patch of the v1.4.x polish-sweep series. Chat-log draw
|
||||
failures surface as a notification, the first-run wizard has an
|
||||
explicit "Later" option, the input history clears on plugin reload,
|
||||
and the status bar version slot stops clipping in narrow windows.
|
||||
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame
|
||||
render cost drops from ~127 ms median to ~76 ms median,
|
||||
comfortably under Dalamud's 100 ms HITCH warning threshold.
|
||||
|
||||
- Chat window draw errors now show a one-shot notification instead
|
||||
of failing silently — stack trace stays in /xllog
|
||||
- First-run wizard: explicit "Later — keep defaults" button.
|
||||
Closing the X no longer silently accepts the defaults; the wizard
|
||||
reopens on the next plugin load if nothing was picked
|
||||
- InputHistoryService clears on plugin dispose so the previous
|
||||
session's typed commands don't bleed into the next load
|
||||
- Status bar hides the version slot when the chat window is too
|
||||
narrow to fit all five slots without overlap
|
||||
- Internal: explicit session-only Auto-Tell-Tab invariant in
|
||||
Plugin.cs plus a pinning test in the Build-Suite
|
||||
- Internal: FontManager falls back to the system font if the
|
||||
embedded Hellion font resource is missing — logs a Warning
|
||||
- First-frame defer: six non-essential rendering sections inside
|
||||
ChatLogWindow skip their first Draw and run one frame later
|
||||
(bottom status bar, channel-name SeString chunks, window bounds
|
||||
check, v0.6.1 hint banner, autocomplete, input-preview
|
||||
calculation). User-visible delay is ~17 ms at 60 fps, hidden
|
||||
inside the post-reload font-atlas build window.
|
||||
- Slash-command centralisation: /hellion, /hellionView,
|
||||
/hellionSeString and /hellionDebugger are registered in
|
||||
LoadAsync instead of inside the corresponding window
|
||||
constructors. The plugin-manager Open and configuration buttons
|
||||
hang on the same path.
|
||||
- Plugin-load profiling logs stay on at Information level
|
||||
(MessageStore connect/migrate, FilterAllTabs, auto-translate
|
||||
warmup) as a regression tripwire — a future load past 100 ms
|
||||
will show up in /xllog without a Debug filter.
|
||||
- ChatTwo IPC compatibility layer: HellionChat now mirrors
|
||||
ChatTwo's full IPC surface (GetChatInputState,
|
||||
ChatInputStateChanged, Register, Unregister, Available,
|
||||
Invoke) under the ChatTwo.* namespace in addition to our
|
||||
existing HellionChat.* provider gates. Third-party
|
||||
integrations that historically only subscribe to ChatTwo's
|
||||
IPC — for example Artisan's and AllaganTools' context-menu
|
||||
hooks — keep working without requiring a code change on their
|
||||
side. Conflict detection prevents ChatTwo from loading in
|
||||
parallel with HellionChat, so there is no slot-collision risk
|
||||
at runtime.
|
||||
- Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using Dalamud.Plugin;
|
||||
using HellionChat.Ipc;
|
||||
using HellionChat.Themes;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace HellionChat.Infrastructure.Hosting;
|
||||
|
||||
// Adapter shells around IHostedService so the host triggers each service's
|
||||
// existing init method without touching the service class itself. Empty
|
||||
// adapters still earn their place: registering them forces an eager resolve
|
||||
// at Build, which runs the service ctor (IPC subscribe etc.) right then
|
||||
// instead of lazily on first GetRequiredService.
|
||||
|
||||
internal sealed class ThemeRegistryInitHostedService(ThemeRegistry registry) : IHostedService
|
||||
{
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// Materialise the lazy AllCustom enumerable so the slug lookup hits a
|
||||
// warm cache; otherwise the first Switch falls through to the built-in
|
||||
// default when Config.Theme points at a custom slug.
|
||||
foreach (var _ in registry.AllCustom()) { }
|
||||
registry.Switch(Plugin.Config.Theme);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
// IPC subscribers do their wiring in the ctor, so StartAsync stays empty —
|
||||
// the registration alone forces an eager resolve which runs that wiring.
|
||||
|
||||
internal sealed class IpcManagerInitHostedService(IpcManager ipc) : IHostedService
|
||||
{
|
||||
private readonly IpcManager _ipc = ipc;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal sealed class TypingIpcInitHostedService(TypingIpc typingIpc) : IHostedService
|
||||
{
|
||||
private readonly TypingIpc _typingIpc = typingIpc;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal sealed class ExtraChatInitHostedService(ExtraChat extraChat) : IHostedService
|
||||
{
|
||||
private readonly ExtraChat _extraChat = extraChat;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal sealed class MessageManagerInitHostedService(
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
MessageManager manager
|
||||
) : IHostedService
|
||||
{
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
// FilterAllTabsAsync rebuilds the per-tab view from the message store;
|
||||
// on Boot, tabs come up empty and the first chat events fill them, so
|
||||
// we skip the rebuild to avoid a pointless full-history scan.
|
||||
if (pluginInterface.Reason is not PluginLoadReason.Boot)
|
||||
manager.FilterAllTabsAsync();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
internal sealed class AutoTellTabsServiceInitHostedService(AutoTellTabsService service)
|
||||
: IHostedService
|
||||
{
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
service.Initialize();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Text;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Infrastructure.Logging;
|
||||
|
||||
internal sealed class DalamudLogger : ILogger
|
||||
{
|
||||
private readonly string _name;
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
public DalamudLogger(string name, IPluginLog pluginLog)
|
||||
{
|
||||
_name = name;
|
||||
_pluginLog = pluginLog;
|
||||
}
|
||||
|
||||
IDisposable? ILogger.BeginScope<TState>(TState state) => default!;
|
||||
|
||||
// Filtering happens in Dalamud's /xllog. Letting every level through keeps
|
||||
// the HellionChat side stateless; if we ever want a per-plugin floor we add
|
||||
// a Config.LogLevel and tighten this method.
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
TState state,
|
||||
Exception? exception,
|
||||
Func<TState, Exception?, string> formatter
|
||||
)
|
||||
{
|
||||
if (!IsEnabled(logLevel))
|
||||
return;
|
||||
|
||||
// U+200B between the bracket and the level is a quiet provenance
|
||||
// marker; byte-distinguishable from any 1:1 port of this format.
|
||||
if ((int)logLevel <= (int)LogLevel.Information)
|
||||
{
|
||||
_pluginLog.Information($"[{_name}]{{{(int)logLevel}}} {state}");
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"[{_name}]{{{(int)logLevel}}} {state} {exception?.Message}");
|
||||
if (!string.IsNullOrWhiteSpace(exception?.StackTrace))
|
||||
sb.AppendLine(exception.StackTrace);
|
||||
|
||||
var inner = exception?.InnerException;
|
||||
while (inner != null)
|
||||
{
|
||||
sb.AppendLine($"InnerException {inner}: {inner.Message}");
|
||||
sb.AppendLine(inner.StackTrace);
|
||||
inner = inner.InnerException;
|
||||
}
|
||||
|
||||
if (logLevel == LogLevel.Warning)
|
||||
_pluginLog.Warning(sb.ToString());
|
||||
else if (logLevel == LogLevel.Error)
|
||||
_pluginLog.Error(sb.ToString());
|
||||
else
|
||||
_pluginLog.Fatal(sb.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Dalamud.Plugin.Services;
|
||||
using HellionChat.Branding;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Infrastructure.Logging;
|
||||
|
||||
[ProviderAlias("Dalamud")]
|
||||
public sealed class DalamudLoggingProvider : ILoggerProvider
|
||||
{
|
||||
// Hellion Forge Bronze (#C2410C). Mixed into the bootstrap fingerprint.
|
||||
private const string HellionMarker = "HellionForgeBronzeC2410C";
|
||||
|
||||
private readonly ConcurrentDictionary<string, DalamudLogger> _loggers = new(
|
||||
StringComparer.OrdinalIgnoreCase
|
||||
);
|
||||
|
||||
private readonly IPluginLog _pluginLog;
|
||||
|
||||
public DalamudLoggingProvider(IPluginLog pluginLog)
|
||||
{
|
||||
_pluginLog = pluginLog;
|
||||
EmitBootstrapBanner();
|
||||
}
|
||||
|
||||
// One-shot per plugin load. Intentionally visible in xllog so uncredited
|
||||
// ports of the DalamudLogger trio keep announcing their origin — the
|
||||
// mini fox silhouette goes first, then the textual provenance line.
|
||||
private void EmitBootstrapBanner()
|
||||
{
|
||||
var version =
|
||||
typeof(DalamudLoggingProvider).Assembly.GetName().Version?.ToString() ?? "0.0.0";
|
||||
var fingerprint = ComputeFingerprint(version);
|
||||
|
||||
foreach (var line in HellionForgeAscii.FoxMini.Split('\n'))
|
||||
{
|
||||
var trimmed = line.TrimEnd('\r');
|
||||
if (trimmed.Length > 0)
|
||||
_pluginLog.Information(trimmed);
|
||||
}
|
||||
|
||||
_pluginLog.Information("by Julia Moon - Hellion Forge");
|
||||
_pluginLog.Information(
|
||||
$"HellionChat DI-Logger bootstrap v{version} fingerprint={fingerprint}"
|
||||
);
|
||||
}
|
||||
|
||||
private static string ComputeFingerprint(string version)
|
||||
{
|
||||
var seed = Encoding.UTF8.GetBytes($"{HellionMarker}-{version}");
|
||||
var hash = SHA256.HashData(seed);
|
||||
var sb = new StringBuilder(8);
|
||||
for (var i = 0; i < 4; i++)
|
||||
sb.Append(hash[i].ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
// Category-name normalisation mirrors Lightless: take the leaf type
|
||||
// name, then either ellipsis-trim long ones or left-pad short ones to
|
||||
// 15 chars so the xllog column stays aligned across services.
|
||||
var catName = categoryName.Split(".", StringSplitOptions.RemoveEmptyEntries).Last();
|
||||
if (catName.Length > 15)
|
||||
catName = string.Concat(
|
||||
catName.AsSpan(0, 6),
|
||||
"...",
|
||||
catName.AsSpan(catName.Length - 6, 6)
|
||||
);
|
||||
else
|
||||
catName = catName.PadLeft(15);
|
||||
|
||||
return _loggers.GetOrAdd(catName, name => new DalamudLogger(name, _pluginLog));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggers.Clear();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Infrastructure.Logging;
|
||||
|
||||
public static class DalamudLoggingProviderExtensions
|
||||
{
|
||||
public static ILoggingBuilder AddDalamudLogging(
|
||||
this ILoggingBuilder builder,
|
||||
IPluginLog pluginLog
|
||||
)
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.Services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<ILoggerProvider, DalamudLoggingProvider>(
|
||||
_ => new DalamudLoggingProvider(pluginLog)
|
||||
)
|
||||
);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace HellionChat.Integrations;
|
||||
@@ -23,7 +24,7 @@ internal sealed class HonorificService : IDisposable
|
||||
private readonly ICallGateSubscriber<object> _ready;
|
||||
private readonly ICallGateSubscriber<object> _disposing;
|
||||
|
||||
private readonly IPluginLog _log;
|
||||
private readonly ILogger<HonorificService> _logger;
|
||||
private readonly IFramework _framework;
|
||||
private bool _versionWarningLogged;
|
||||
|
||||
@@ -34,12 +35,12 @@ internal sealed class HonorificService : IDisposable
|
||||
|
||||
public HonorificService(
|
||||
IDalamudPluginInterface pluginInterface,
|
||||
IPluginLog log,
|
||||
ILogger<HonorificService> logger,
|
||||
IFramework framework
|
||||
)
|
||||
{
|
||||
_framework = framework;
|
||||
_log = log;
|
||||
_logger = logger;
|
||||
|
||||
// Gate objects are cached per-name by Dalamud and safe to register
|
||||
// before Honorific loads — they just won't fire until it does.
|
||||
@@ -84,7 +85,7 @@ internal sealed class HonorificService : IDisposable
|
||||
{
|
||||
if (!_versionWarningLogged)
|
||||
{
|
||||
_log.Warning(
|
||||
_logger.LogWarning(
|
||||
"Honorific API version mismatch — expected major 3, "
|
||||
+ "found {Major}.{Minor}. Disabling Honorific integration.",
|
||||
version.Item1,
|
||||
@@ -104,7 +105,7 @@ internal sealed class HonorificService : IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Honorific not installed or not yet initialised — Ready will retry.
|
||||
_log.Debug(ex, "Honorific not available at HellionChat startup; awaiting Ready.");
|
||||
_logger.LogDebug(ex, "Honorific not available at HellionChat startup; awaiting Ready.");
|
||||
IsAvailable = false;
|
||||
CurrentTitle = null;
|
||||
}
|
||||
@@ -149,7 +150,7 @@ internal sealed class HonorificService : IDisposable
|
||||
{
|
||||
// Warning not Debug — a silent unsubscribe failure leaks a live
|
||||
// subscription across plugin reloads.
|
||||
_log.Warning(
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Honorific unsubscribe failed (likely API break or gate already gone)."
|
||||
);
|
||||
|
||||
@@ -5,11 +5,11 @@ namespace HellionChat.Integrations;
|
||||
// Local DTO mirroring Honorific's TitleData — no hard reference to Honorific.dll
|
||||
// so HellionChat loads cleanly when Honorific is absent.
|
||||
//
|
||||
// v1.4.7: render Glow only. Gradient (Color3 / GradientColourSet / Style) is
|
||||
// parsed and stashed so a future cycle can render it without re-shaping the
|
||||
// JSON roundtrip — see vault anchor "Honorific Full Gradient Port" (would
|
||||
// need GradientSystem.cs + the hardcoded Pride-palette list ported, or an
|
||||
// upstream IPC PR exposing the resolved frame colour).
|
||||
// Only Glow is rendered. Color3, GradientColourSet and GradientAnimationStyle
|
||||
// are parsed but unused — the animated gradient lives entirely inside Honorific
|
||||
// and is not exposed over IPC, so reproducing it here would mean shipping our
|
||||
// own copy of Honorific's colour palette. The fields stay in the DTO so the
|
||||
// JSON roundtrip remains lossless.
|
||||
internal sealed record HonorificTitleData(
|
||||
string? Title,
|
||||
bool IsPrefix,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ipc;
|
||||
|
||||
public sealed class ExtraChat : IDisposable
|
||||
{
|
||||
private readonly ILogger<ExtraChat> _logger;
|
||||
|
||||
#pragma warning disable CS0649 // Assigned through IPC
|
||||
[Serializable]
|
||||
private struct OverrideInfo
|
||||
@@ -36,8 +39,9 @@ public sealed class ExtraChat : IDisposable
|
||||
private volatile Dictionary<Guid, string> ChannelNamesInternal = new();
|
||||
internal IReadOnlyDictionary<Guid, string> ChannelNames => ChannelNamesInternal;
|
||||
|
||||
internal ExtraChat()
|
||||
internal ExtraChat(ILogger<ExtraChat> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
OverrideChannelGate = Plugin.Interface.GetIpcSubscriber<OverrideInfo, object>(
|
||||
"ExtraChat.OverrideChannelColour"
|
||||
);
|
||||
@@ -62,10 +66,7 @@ public sealed class ExtraChat : IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
// ExtraChat is optional; IPC failure is normal when the plugin isn't loaded.
|
||||
Plugin.LogProxy.Verbose(
|
||||
ex,
|
||||
"ExtraChat IPC initial state query failed (peer not loaded?)"
|
||||
);
|
||||
_logger.LogTrace(ex, "ExtraChat IPC initial state query failed (peer not loaded?)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using HellionChat.Code;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ipc;
|
||||
|
||||
@@ -19,12 +20,26 @@ internal sealed class TypingIpc : IDisposable
|
||||
private ICallGateProvider<ChatInputState> StateQueryGate { get; }
|
||||
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
|
||||
|
||||
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Some third-party plugins
|
||||
// have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
|
||||
// gates. HellionChat replaces ChatTwo (conflict detection prevents
|
||||
// parallel loading), so mirroring the ChatTwo provider slots lets those
|
||||
// plugins keep working without code changes on their side. The tuple
|
||||
// shape is textually identical to ChatTwo's IPC surface (same member
|
||||
// order, same underlying types — ChatType is `ushort` in both repos)
|
||||
// so Dalamud's IPC marshalling matches across plugin boundaries.
|
||||
private ICallGateProvider<ChatInputState> ChatTwoStateQueryGate { get; }
|
||||
private ICallGateProvider<ChatInputState, object?> ChatTwoStateChangedGate { get; }
|
||||
|
||||
private ChatInputState LastState;
|
||||
private bool HasState;
|
||||
|
||||
internal TypingIpc(Plugin plugin)
|
||||
private readonly ILogger<TypingIpc> _logger;
|
||||
|
||||
internal TypingIpc(Plugin plugin, ILogger<TypingIpc> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
|
||||
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
|
||||
"HellionChat.GetChatInputState"
|
||||
@@ -33,7 +48,16 @@ internal sealed class TypingIpc : IDisposable
|
||||
"HellionChat.ChatInputStateChanged"
|
||||
);
|
||||
|
||||
// v1.4.9 R4: ChatTwo-prefixed compatibility slots (see class-level comment).
|
||||
ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
|
||||
"ChatTwo.GetChatInputState"
|
||||
);
|
||||
ChatTwoStateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
|
||||
"ChatTwo.ChatInputStateChanged"
|
||||
);
|
||||
|
||||
StateQueryGate.RegisterFunc(GetState);
|
||||
ChatTwoStateQueryGate.RegisterFunc(GetState);
|
||||
}
|
||||
|
||||
private ChatInputState BuildState()
|
||||
@@ -67,10 +91,13 @@ internal sealed class TypingIpc : IDisposable
|
||||
HasState = true;
|
||||
LastState = state;
|
||||
StateChangedGate.SendMessage(state);
|
||||
// v1.4.9 R4: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
|
||||
ChatTwoStateChangedGate.SendMessage(state);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StateQueryGate.UnregisterFunc();
|
||||
ChatTwoStateQueryGate.UnregisterFunc();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
internal sealed class IpcManager : IDisposable
|
||||
{
|
||||
private readonly ILogger<IpcManager> _logger;
|
||||
|
||||
private ICallGateProvider<string> RegisterGate { get; }
|
||||
private ICallGateProvider<string, object?> UnregisterGate { get; }
|
||||
private ICallGateProvider<object?> AvailableGate { get; }
|
||||
@@ -19,10 +22,31 @@ internal sealed class IpcManager : IDisposable
|
||||
object?
|
||||
> InvokeGate { get; }
|
||||
|
||||
// v1.4.9 R4: ChatTwo IPC compatibility mirror. Third-party plugins with
|
||||
// a no-fork policy (e.g. Artisan, AllaganTools) only subscribe to the
|
||||
// ChatTwo.*-prefixed context-menu integration gates. Mirroring all four
|
||||
// provider slots under the ChatTwo namespace lets those plugins keep
|
||||
// working without code changes on their side. Conflict detection
|
||||
// prevents ChatTwo and HellionChat from loading in parallel, so no slot
|
||||
// collision risk.
|
||||
private ICallGateProvider<string> ChatTwoRegisterGate { get; }
|
||||
private ICallGateProvider<string, object?> ChatTwoUnregisterGate { get; }
|
||||
private ICallGateProvider<object?> ChatTwoAvailableGate { get; }
|
||||
private ICallGateProvider<
|
||||
string,
|
||||
PlayerPayload?,
|
||||
ulong,
|
||||
Payload?,
|
||||
SeString?,
|
||||
SeString?,
|
||||
object?
|
||||
> ChatTwoInvokeGate { get; }
|
||||
|
||||
internal List<string> Registered { get; } = [];
|
||||
|
||||
public IpcManager()
|
||||
public IpcManager(ILogger<IpcManager> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
RegisterGate = Plugin.Interface.GetIpcProvider<string>("HellionChat.Register");
|
||||
RegisterGate.RegisterFunc(Register);
|
||||
|
||||
@@ -41,7 +65,32 @@ internal sealed class IpcManager : IDisposable
|
||||
object?
|
||||
>("HellionChat.Invoke");
|
||||
|
||||
// v1.4.9 R4: ChatTwo-prefixed mirrors of the four context-menu slots
|
||||
// above. Share the same Register/Unregister backing methods so a
|
||||
// plugin that subscribes via either namespace lands in the same
|
||||
// Registered list. SendMessage on Invoke fans out to both gates.
|
||||
ChatTwoRegisterGate = Plugin.Interface.GetIpcProvider<string>("ChatTwo.Register");
|
||||
ChatTwoRegisterGate.RegisterFunc(Register);
|
||||
|
||||
ChatTwoAvailableGate = Plugin.Interface.GetIpcProvider<object?>("ChatTwo.Available");
|
||||
|
||||
ChatTwoUnregisterGate = Plugin.Interface.GetIpcProvider<string, object?>(
|
||||
"ChatTwo.Unregister"
|
||||
);
|
||||
ChatTwoUnregisterGate.RegisterAction(Unregister);
|
||||
|
||||
ChatTwoInvokeGate = Plugin.Interface.GetIpcProvider<
|
||||
string,
|
||||
PlayerPayload?,
|
||||
ulong,
|
||||
Payload?,
|
||||
SeString?,
|
||||
SeString?,
|
||||
object?
|
||||
>("ChatTwo.Invoke");
|
||||
|
||||
AvailableGate.SendMessage();
|
||||
ChatTwoAvailableGate.SendMessage();
|
||||
}
|
||||
|
||||
internal void Invoke(
|
||||
@@ -54,6 +103,8 @@ internal sealed class IpcManager : IDisposable
|
||||
)
|
||||
{
|
||||
InvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
|
||||
// v1.4.9 R4: fan out the same event to plugins listening on ChatTwo.Invoke.
|
||||
ChatTwoInvokeGate.SendMessage(id, sender, contentId, payload, senderString, content);
|
||||
}
|
||||
|
||||
private string Register()
|
||||
@@ -72,6 +123,8 @@ internal sealed class IpcManager : IDisposable
|
||||
{
|
||||
UnregisterGate.UnregisterAction();
|
||||
RegisterGate.UnregisterFunc();
|
||||
ChatTwoUnregisterGate.UnregisterAction();
|
||||
ChatTwoRegisterGate.UnregisterFunc();
|
||||
Registered.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using HellionChat.Util;
|
||||
using Lumina.Text.Expressions;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
@@ -22,6 +23,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
internal const int MessageDisplayLimit = 10_000;
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
private readonly ILogger<MessageManager> _logger;
|
||||
internal MessageStore Store { get; }
|
||||
|
||||
private Dictionary<ChatType, NameFormatting> Formats { get; } = [];
|
||||
@@ -48,11 +50,21 @@ internal class MessageManager : IAsyncDisposable
|
||||
// AutoTellTabsService to spawn or refresh temp tabs without coupling.
|
||||
public event Action<Message>? MessageProcessed;
|
||||
|
||||
internal unsafe MessageManager(Plugin plugin)
|
||||
internal unsafe MessageManager(
|
||||
Plugin plugin,
|
||||
ILogger<MessageManager> logger,
|
||||
ILoggerFactory loggerFactory
|
||||
)
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
|
||||
Store = new MessageStore(DatabasePath(), Plugin.PlatformUtil, Plugin.LogProxy);
|
||||
Store = new MessageStore(
|
||||
DatabasePath(),
|
||||
Plugin.PlatformUtil,
|
||||
loggerFactory.CreateLogger<MessageStore>(),
|
||||
loggerFactory
|
||||
);
|
||||
|
||||
PendingMessageThread = new Thread(() =>
|
||||
ProcessPendingMessages(PendingThreadCancellationToken.Token)
|
||||
@@ -91,7 +103,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
await Task.Delay(100);
|
||||
|
||||
if (PendingMessageThread.IsAlive)
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
"PendingMessageThread did not observe cancellation within 10s. "
|
||||
+ "Worker remains on background thread; next plugin reload releases it."
|
||||
);
|
||||
@@ -137,7 +149,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error processing pending message");
|
||||
_logger.LogError(ex, "Error processing pending message");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -182,12 +194,12 @@ internal class MessageManager : IAsyncDisposable
|
||||
|
||||
// Mark failed messages as deleted to prevent retry attempts
|
||||
var failedIds = messages.FailedMessageIds();
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"Marking {failedIds.Count} messages as deleted due to parse failures"
|
||||
);
|
||||
foreach (var msgId in messages.FailedMessageIds())
|
||||
{
|
||||
Plugin.LogProxy.Debug($"Marking message '{msgId}' as deleted due to parse failure");
|
||||
_logger.LogDebug($"Marking message '{msgId}' as deleted due to parse failure");
|
||||
Store.DeleteMessage(msgId);
|
||||
}
|
||||
}
|
||||
@@ -203,10 +215,13 @@ internal class MessageManager : IAsyncDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in FilterAllTabs");
|
||||
_logger.LogError(ex, "Error in FilterAllTabs");
|
||||
}
|
||||
|
||||
Plugin.LogProxy.Debug($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
||||
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
||||
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
||||
// regressions; remains in place after Sub-Task 3.4 Befund.
|
||||
_logger.LogInformation($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,7 +276,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error in ContentIdResolver");
|
||||
_logger.LogError(ex, "Error in ContentIdResolver");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+67
-28
@@ -9,6 +9,7 @@ using MessagePack;
|
||||
using MessagePack.Formatters;
|
||||
using MessagePack.Resolvers;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Encoding = System.Text.Encoding;
|
||||
|
||||
namespace HellionChat;
|
||||
@@ -179,7 +180,8 @@ internal class MessageStore : IDisposable
|
||||
}
|
||||
|
||||
private readonly IPlatformUtil _platformUtil;
|
||||
private readonly IPluginLogProxy _logger;
|
||||
private readonly ILogger<MessageStore> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
// Readiness gate for the FTS5 full-text index. Volatile so the DbViewer's
|
||||
// per-frame IsFtsIndexBuilt read sees the flip the moment the bulk-insert
|
||||
@@ -197,11 +199,17 @@ internal class MessageStore : IDisposable
|
||||
// own SqliteConnection via OpenSecondaryConnection.
|
||||
private readonly object _readLock = new();
|
||||
|
||||
internal MessageStore(string dbPath, IPlatformUtil platformUtil, IPluginLogProxy logger)
|
||||
internal MessageStore(
|
||||
string dbPath,
|
||||
IPlatformUtil platformUtil,
|
||||
ILogger<MessageStore> logger,
|
||||
ILoggerFactory loggerFactory
|
||||
)
|
||||
{
|
||||
DbPath = dbPath;
|
||||
_platformUtil = platformUtil;
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
Connection = Connect();
|
||||
Migrate();
|
||||
InitFtsReadyCache();
|
||||
@@ -237,14 +245,26 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private SqliteConnection Connect()
|
||||
{
|
||||
// v1.4.9 R3 profiling: trace cost of SQLite open + pragma-apply. Paired
|
||||
// with the Migrate-Stopwatch below — Connect alone is the cheap half
|
||||
// (Open + a handful of PRAGMAs); the expensive half typically lives in
|
||||
// Migrate, especially on a large DB after a schema bump.
|
||||
var connectSw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var conn = new SqliteConnection(BuildConnectionString(DbPath));
|
||||
conn.Open();
|
||||
ApplyPragmas(conn);
|
||||
connectSw.Stop();
|
||||
_logger.LogInformation($"MessageStore.Connect took {connectSw.ElapsedMilliseconds}ms");
|
||||
return conn;
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
// v1.4.9 R3 profiling: trace cost of the schema-migration chain. On a
|
||||
// large DB after a fresh schema bump this is the dominant SQLite cost
|
||||
// at plugin-load, not Connect.
|
||||
var migrateSw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = "PRAGMA user_version;";
|
||||
var userVersion = Convert.ToInt32(cmd.ExecuteScalar());
|
||||
@@ -276,11 +296,14 @@ internal class MessageStore : IDisposable
|
||||
|
||||
foreach (var migration in migrationsToDo)
|
||||
migration();
|
||||
|
||||
migrateSw.Stop();
|
||||
_logger.LogInformation($"MessageStore.Migrate took {migrateSw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
private void Migrate0()
|
||||
{
|
||||
_logger.Information("Running migration 0: Creating tables");
|
||||
_logger.LogInformation("Running migration 0: Creating tables");
|
||||
Connection.Execute(
|
||||
@"
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
@@ -307,7 +330,7 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private void Migrate1()
|
||||
{
|
||||
_logger.Information("Running migration 1: Adding Deleted column");
|
||||
_logger.LogInformation("Running migration 1: Adding Deleted column");
|
||||
Connection.Execute(
|
||||
@"
|
||||
ALTER TABLE messages ADD COLUMN Deleted BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -319,7 +342,7 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private void Migrate2()
|
||||
{
|
||||
_logger.Information("Running migration 2: Adding Channel generated column");
|
||||
_logger.LogInformation("Running migration 2: Adding Channel generated column");
|
||||
Connection.Execute(
|
||||
@"
|
||||
ALTER TABLE messages ADD COLUMN Channel INTEGER GENERATED ALWAYS AS (Code & 0x7f) VIRTUAL;
|
||||
@@ -347,13 +370,15 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private void Migrate3()
|
||||
{
|
||||
_logger.Information("Running migration 3: Fix log kinds to fit the new format");
|
||||
_logger.LogInformation("Running migration 3: Fix log kinds to fit the new format");
|
||||
|
||||
// Recovery for partially-applied Migrate3: schema already in target
|
||||
// shape but user_version was never bumped -- just record and exit.
|
||||
if (ColumnExists("messages", "ChatType") && !ColumnExists("messages", "Code"))
|
||||
{
|
||||
_logger.Information("Migration 3: schema already migrated, only bumping user_version");
|
||||
_logger.LogInformation(
|
||||
"Migration 3: schema already migrated, only bumping user_version"
|
||||
);
|
||||
SetMigrationVersion(3);
|
||||
return;
|
||||
}
|
||||
@@ -383,7 +408,7 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private void Migrate4()
|
||||
{
|
||||
_logger.Information("Running migration 4: Add FTS5 virtual table for full-text search");
|
||||
_logger.LogInformation("Running migration 4: Add FTS5 virtual table for full-text search");
|
||||
|
||||
// Standalone FTS5 table (no content='messages' linking, no content_rowid).
|
||||
// messages.Id is BLOB-PK (Guid), which is incompatible with FTS5's
|
||||
@@ -407,7 +432,7 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private void SetMigrationVersion(int version)
|
||||
{
|
||||
_logger.Information($"Setting version {version}");
|
||||
_logger.LogInformation($"Setting version {version}");
|
||||
using var cmd = Connection.CreateCommand();
|
||||
// PRAGMA does not accept SQLite parameter bindings; version is a
|
||||
// compile-time int from the migration sequence, never user input.
|
||||
@@ -822,7 +847,7 @@ internal class MessageStore : IDisposable
|
||||
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
||||
if (!Plugin.Config.IsAllowedForStorage(message.Code.Type))
|
||||
{
|
||||
_logger.Verbose($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
||||
_logger.LogTrace($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -926,7 +951,10 @@ internal class MessageStore : IDisposable
|
||||
if (to is not null)
|
||||
cmd.Parameters.AddWithValue("$To", to.Value.ToUnixTimeMilliseconds());
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
||||
return new MessageEnumerator(
|
||||
cmd.ExecuteReader(),
|
||||
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,20 +1006,24 @@ internal class MessageStore : IDisposable
|
||||
|
||||
cmd.Parameters.AddWithValue("$Count", count);
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
||||
return new MessageEnumerator(
|
||||
cmd.ExecuteReader(),
|
||||
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns up to limit tells exchanged with the named player, oldest-first.
|
||||
// SQL narrows by Receiver + ChatType (indexed); client does the final
|
||||
// PlayerPayload comparison. sqlScanLimit caps the scan to stay within
|
||||
// the message-processing worker thread budget.
|
||||
// Returns up to `limit` tells exchanged with the named player, oldest-first.
|
||||
// SQL narrows by Receiver + ChatType via the (Receiver, Date) index, then
|
||||
// the client-side loop runs PlayerPayload comparison and breaks once
|
||||
// `limit` partner matches accumulate. Earlier versions had a hardcoded
|
||||
// 500-row scan cap that cut less-frequent pinned partners off the back of
|
||||
// the window in chatty sessions; removed in v1.4.10.
|
||||
internal IReadOnlyList<Message> GetTellHistoryWithSender(
|
||||
ulong receiver,
|
||||
string senderName,
|
||||
uint senderWorld,
|
||||
int limit,
|
||||
int sqlScanLimit = 500
|
||||
int limit
|
||||
)
|
||||
{
|
||||
if (limit <= 0)
|
||||
@@ -1009,17 +1041,18 @@ internal class MessageStore : IDisposable
|
||||
WHERE deleted = false
|
||||
AND Receiver = $Receiver
|
||||
AND ChatType IN ($TellIncoming, $TellOutgoing)
|
||||
ORDER BY Date DESC
|
||||
LIMIT $ScanLimit;
|
||||
ORDER BY Date DESC;
|
||||
";
|
||||
cmd.CommandTimeout = 60;
|
||||
cmd.Parameters.AddWithValue("$Receiver", receiver);
|
||||
cmd.Parameters.AddWithValue("$TellIncoming", (int)ChatType.TellIncoming);
|
||||
cmd.Parameters.AddWithValue("$TellOutgoing", (int)ChatType.TellOutgoing);
|
||||
cmd.Parameters.AddWithValue("$ScanLimit", sqlScanLimit);
|
||||
|
||||
var collected = new List<Message>();
|
||||
using var enumerator = new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
||||
using var enumerator = new MessageEnumerator(
|
||||
cmd.ExecuteReader(),
|
||||
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||
);
|
||||
foreach (var message in enumerator)
|
||||
{
|
||||
if (!ChunkUtil.MatchesSender(message, senderName, senderWorld))
|
||||
@@ -1131,7 +1164,10 @@ internal class MessageStore : IDisposable
|
||||
((DateTimeOffset)before).ToUnixTimeMilliseconds()
|
||||
);
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
||||
return new MessageEnumerator(
|
||||
cmd.ExecuteReader(),
|
||||
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1184,7 +1220,10 @@ internal class MessageStore : IDisposable
|
||||
cmd.Parameters.AddWithValue("$Offset", DbViewer.RowPerPage * page);
|
||||
cmd.Parameters.AddWithValue("$OffsetCount", DbViewer.RowPerPage);
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
||||
return new MessageEnumerator(
|
||||
cmd.ExecuteReader(),
|
||||
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1205,14 +1244,14 @@ internal class MessageStore : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageEnumerator(DbDataReader reader, IPluginLogProxy logger)
|
||||
internal class MessageEnumerator(DbDataReader reader, ILogger<MessageEnumerator> logger)
|
||||
: IEnumerable<Message>,
|
||||
IDisposable,
|
||||
IAsyncDisposable
|
||||
{
|
||||
private const int MaxErrorLogs = 10;
|
||||
|
||||
private readonly IPluginLogProxy _logger = logger;
|
||||
private readonly ILogger<MessageEnumerator> _logger = logger;
|
||||
private readonly List<Guid> FailedIds = [];
|
||||
private int FailedCount;
|
||||
public bool DidError => FailedCount > 0;
|
||||
@@ -1233,10 +1272,10 @@ internal class MessageEnumerator(DbDataReader reader, IPluginLogProxy logger)
|
||||
catch (Exception e)
|
||||
{
|
||||
if (FailedCount < MaxErrorLogs)
|
||||
_logger.Error($"Exception while reading message '{id}' from database: {e}");
|
||||
_logger.LogError($"Exception while reading message '{id}' from database: {e}");
|
||||
FailedCount++;
|
||||
if (FailedCount == MaxErrorLogs)
|
||||
_logger.Error("Further parsing errors will not be logged");
|
||||
_logger.LogError("Further parsing errors will not be logged");
|
||||
if (id != Guid.Empty)
|
||||
FailedIds.Add(id);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using HellionChat.Resources;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Action = System.Action;
|
||||
using ChatTwoPartyFinderPayload = HellionChat.Util.PartyFinderPayload;
|
||||
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
||||
@@ -40,9 +41,12 @@ public sealed class PayloadHandler
|
||||
|
||||
private const uint PopupSfx = 1;
|
||||
|
||||
internal PayloadHandler(ChatLogWindow logWindow)
|
||||
private readonly ILogger<PayloadHandler> _logger;
|
||||
|
||||
internal PayloadHandler(ChatLogWindow logWindow, ILogger<PayloadHandler> logger)
|
||||
{
|
||||
LogWindow = logWindow;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
internal void Draw()
|
||||
@@ -131,7 +135,7 @@ public sealed class PayloadHandler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error executing integration");
|
||||
_logger.LogError(ex, "Error executing integration");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +539,7 @@ public sealed class PayloadHandler
|
||||
)
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Warning("Could not find DalamudLinkHandlers");
|
||||
_logger.LogWarning("Could not find DalamudLinkHandlers");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -546,7 +550,7 @@ public sealed class PayloadHandler
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error executing DalamudLinkPayload handler");
|
||||
_logger.LogError(ex, "Error executing DalamudLinkPayload handler");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+214
-104
@@ -15,6 +15,8 @@ using HellionChat.Resources;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
@@ -123,6 +125,20 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// isolation. Wired immediately after Dalamud injects Log.
|
||||
internal static IPluginLogProxy LogProxy { get; private set; } = null!;
|
||||
|
||||
// Nullable so DisposeAsync stays safe if Host-build throws before the
|
||||
// fields get assigned — Dalamud fires DisposeAsync regardless.
|
||||
private readonly IHost? _host;
|
||||
private readonly PluginLifecycle? _lifecycle;
|
||||
|
||||
// Wrapper cached so TearDown can detach the live instance instead of
|
||||
// re-registering with identical args (v1.4.9 ISSUE-1 cleanup).
|
||||
private CommandWrapper? _hellionSettingsCmd;
|
||||
private CommandWrapper? _hellionViewCmd;
|
||||
private CommandWrapper? _hellionDebuggerCmd;
|
||||
#if DEBUG
|
||||
private CommandWrapper? _hellionSeStringCmd;
|
||||
#endif
|
||||
|
||||
// Idempotency guard — Dalamud may fire DisposeAsync twice in a reload race.
|
||||
private int _disposeStarted;
|
||||
|
||||
@@ -176,11 +192,11 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
Config = Interface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
|
||||
// Wire platform indirection before LoadAsync allocates anything that
|
||||
// needs Util.* — services then read Plugin.PlatformUtil instead of
|
||||
// hitting the Dalamud static surface directly.
|
||||
PlatformUtil = new DalamudPlatformUtil();
|
||||
LogProxy = new DalamudPluginLogProxy(Log);
|
||||
// PlatformUtil and LogProxy are filled from the DI container in
|
||||
// Phase-1 below (`_host.Services.GetRequiredService<IPlatformUtil>()`
|
||||
// and the LogProxy equivalent). Phase-0 helpers that run before that
|
||||
// point (MigrateFromChatTwoLayout, LanguageChanged, ImGuiUtil.Initialize)
|
||||
// do not touch either static, so the brief null-window is safe.
|
||||
|
||||
// Schema gate: v1.4.x requires config v16+. Users on older schemas
|
||||
// must install v1.4.2 first to run the migration chain. v17 adds
|
||||
@@ -189,8 +205,8 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"HellionChat v1.4.8 requires config schema v16, got v{Config.Version}. "
|
||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.8."
|
||||
$"HellionChat v1.4.10 requires config schema v16, got v{Config.Version}. "
|
||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
|
||||
);
|
||||
}
|
||||
Config.Version = 17;
|
||||
@@ -203,6 +219,72 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
ImGuiUtil.Initialize(this);
|
||||
|
||||
DeferredSaveFrames = -1;
|
||||
|
||||
// Custom themes dir + seed run before the container builds so the
|
||||
// ThemeRegistry factory lambda finds the directory ready.
|
||||
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(customThemesDir);
|
||||
SeedExampleThemeIfEmpty(customThemesDir);
|
||||
|
||||
// Phase-1: build the host synchronously (the schema gate must clear
|
||||
// before services allocate; Lightless' deferred build would invert
|
||||
// that order) and pull singletons into the Plugin.X surface.
|
||||
var dependencies = new PluginHostDependencies(
|
||||
Interface,
|
||||
Log,
|
||||
ChatGui,
|
||||
ClientState,
|
||||
CommandManager,
|
||||
Condition,
|
||||
DataManager,
|
||||
Framework,
|
||||
GameGui,
|
||||
KeyState,
|
||||
ObjectTable,
|
||||
PartyList,
|
||||
TargetManager,
|
||||
TextureProvider,
|
||||
GameInteropProvider,
|
||||
GameConfig,
|
||||
Notification,
|
||||
AddonLifecycle,
|
||||
PlayerState,
|
||||
Evaluator,
|
||||
SelfTestRegistry
|
||||
);
|
||||
|
||||
_host = PluginHostFactory.Build(this, dependencies);
|
||||
_lifecycle = _host.Services.GetRequiredService<PluginLifecycle>();
|
||||
_lifecycle.Host = _host;
|
||||
|
||||
// Plugin.X static bridge - filled from the container so DI-aware code
|
||||
// and the ~93 Plugin.X consumer sites read the same instances.
|
||||
PlatformUtil = _host.Services.GetRequiredService<IPlatformUtil>();
|
||||
LogProxy = _host.Services.GetRequiredService<IPluginLogProxy>();
|
||||
FileDialogManager = _host.Services.GetRequiredService<FileDialogManager>();
|
||||
|
||||
// Resolve order matters: block-B services first so the windows can
|
||||
// read Plugin.MessageManager etc. from their own ctors without NREs.
|
||||
FontManager = _host.Services.GetRequiredService<FontManager>();
|
||||
ThemeRegistry = _host.Services.GetRequiredService<Themes.ThemeRegistry>();
|
||||
Commands = _host.Services.GetRequiredService<Commands>();
|
||||
Functions = _host.Services.GetRequiredService<GameFunctions.GameFunctions>();
|
||||
Ipc = _host.Services.GetRequiredService<IpcManager>();
|
||||
TypingIpc = _host.Services.GetRequiredService<TypingIpc>();
|
||||
ExtraChat = _host.Services.GetRequiredService<ExtraChat>();
|
||||
HonorificService = _host.Services.GetRequiredService<Integrations.HonorificService>();
|
||||
StatusBar = _host.Services.GetRequiredService<Ui.StatusBar>();
|
||||
MessageManager = _host.Services.GetRequiredService<MessageManager>();
|
||||
AutoTellTabsService = _host.Services.GetRequiredService<AutoTellTabsService>();
|
||||
|
||||
ChatLogWindow = _host.Services.GetRequiredService<ChatLogWindow>();
|
||||
SettingsWindow = _host.Services.GetRequiredService<SettingsWindow>();
|
||||
DbViewer = _host.Services.GetRequiredService<DbViewer>();
|
||||
InputPreview = _host.Services.GetRequiredService<InputPreview>();
|
||||
CommandHelpWindow = _host.Services.GetRequiredService<CommandHelpWindow>();
|
||||
SeStringDebugger = _host.Services.GetRequiredService<SeStringDebugger>();
|
||||
DebuggerWindow = _host.Services.GetRequiredService<DebuggerWindow>();
|
||||
FirstRunWizard = _host.Services.GetRequiredService<FirstRunWizard>();
|
||||
}
|
||||
|
||||
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||
@@ -224,71 +306,34 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// BuildFonts registers handles with Dalamud's FontAtlas; the atlas
|
||||
// rebuilds async a few frames later (visible "font-pop" on first load).
|
||||
FontManager = new FontManager();
|
||||
FontManager.BuildFonts();
|
||||
// Container drives service init now: Host.StartAsync triggers the
|
||||
// remaining IHostedService adapters (ThemeRegistry cache warmup +
|
||||
// Switch, IPC eager-resolve, MessageManager FilterAllTabsAsync,
|
||||
// AutoTellTabsService.Initialize). FontManager runs its own init
|
||||
// inline inside the ctor's SuppressAutoRebuild block on eager
|
||||
// resolve. Window registration with WindowSystem runs on the
|
||||
// framework thread inside PluginLifecycle.LoadAsync after
|
||||
// StartAsync returns.
|
||||
if (_lifecycle is not null)
|
||||
await _lifecycle.LoadAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// ThemeRegistry must be wired before the first Draw tick.
|
||||
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(customThemesDir);
|
||||
SeedExampleThemeIfEmpty(customThemesDir);
|
||||
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
||||
// Warm up the custom-theme cache before the first Switch.
|
||||
// LoadCustomBySlug is a reverse-lookup over _customCache; on a
|
||||
// cold cache a Config.Theme that points at a custom slug would
|
||||
// fall through to the built-in default. AllCustom is a lazy
|
||||
// enumerable, so iterate it explicitly to materialise the cache.
|
||||
foreach (var _ in ThemeRegistry.AllCustom()) { }
|
||||
ThemeRegistry.Switch(Config.Theme);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Service allocations — order encodes dependencies.
|
||||
// HonorificService registers IPC subscribers early to catch
|
||||
// Ready/Disposing events from the first frame.
|
||||
FileDialogManager = new FileDialogManager();
|
||||
Commands = new Commands();
|
||||
Functions = new GameFunctions.GameFunctions(this);
|
||||
Ipc = new IpcManager();
|
||||
TypingIpc = new TypingIpc(this);
|
||||
ExtraChat = new ExtraChat();
|
||||
HonorificService = new Integrations.HonorificService(Interface, Log, Framework);
|
||||
StatusBar = new Ui.StatusBar();
|
||||
MessageManager = new MessageManager(this);
|
||||
|
||||
AutoTellTabsService = new AutoTellTabsService(
|
||||
this,
|
||||
MessageManager,
|
||||
MessageManager.Store
|
||||
);
|
||||
AutoTellTabsService.Initialize();
|
||||
|
||||
SelfTestRegistry.RegisterTestSteps([new SelfTests.ThemeSwitchSelfTestStep(this)]);
|
||||
|
||||
ChatLogWindow = new ChatLogWindow(this);
|
||||
SettingsWindow = new SettingsWindow(this);
|
||||
DbViewer = new DbViewer(this);
|
||||
InputPreview = new InputPreview(ChatLogWindow);
|
||||
CommandHelpWindow = new CommandHelpWindow(ChatLogWindow);
|
||||
SeStringDebugger = new SeStringDebugger(this);
|
||||
DebuggerWindow = new DebuggerWindow(this);
|
||||
FirstRunWizard = new FirstRunWizard(this);
|
||||
|
||||
WindowSystem.AddWindow(ChatLogWindow);
|
||||
WindowSystem.AddWindow(SettingsWindow);
|
||||
WindowSystem.AddWindow(DbViewer);
|
||||
WindowSystem.AddWindow(InputPreview);
|
||||
WindowSystem.AddWindow(CommandHelpWindow);
|
||||
WindowSystem.AddWindow(SeStringDebugger);
|
||||
WindowSystem.AddWindow(DebuggerWindow);
|
||||
WindowSystem.AddWindow(FirstRunWizard);
|
||||
SelfTestRegistry.RegisterTestSteps([
|
||||
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||
new SelfTests.FontManagerCtorSmokeStep(this),
|
||||
new SelfTests.FontPushSmokeStep(this),
|
||||
]);
|
||||
|
||||
if (!Config.FirstRunCompleted)
|
||||
FirstRunWizard.IsOpen = true;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Populate the command dictionary + UiBuilder hooks BEFORE
|
||||
// Commands.Initialise() walks the dictionary and registers each
|
||||
// entry with Dalamud's CommandManager (Commands.cs:15-28). Adding
|
||||
// wrappers after Initialise() would leak them — they'd live in
|
||||
// the dictionary but never reach Dalamud.
|
||||
SetupCommands();
|
||||
Commands.Initialise();
|
||||
|
||||
// Daily retention sweep — fire-and-forget, skips when disabled
|
||||
@@ -298,8 +343,8 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
if (Config.ShowEmotes)
|
||||
_ = EmoteCache.LoadData();
|
||||
|
||||
if (Interface.Reason is not PluginLoadReason.Boot)
|
||||
MessageManager.FilterAllTabsAsync();
|
||||
// FilterAllTabsAsync now runs from MessageManagerInitHostedService
|
||||
// during Host.StartAsync (same Reason-not-Boot guard there).
|
||||
|
||||
// Kick the FTS5 rebuild worker if Migrate4 just added the schema or
|
||||
// a previous run was cut short (InitFtsReadyCache leaves _ftsReady
|
||||
@@ -424,7 +469,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
Framework.Update += FrameworkUpdate;
|
||||
Interface.UiBuilder.Draw += Draw;
|
||||
Interface.LanguageChanged += LanguageChanged;
|
||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -455,7 +499,6 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
Exception? failure = null;
|
||||
|
||||
// Unsubscribe hooks first — mirrors the hooks-last subscribe order in LoadAsync.
|
||||
failure = CaptureFailure(failure, () => Interface.UiBuilder.OpenMainUi -= OpenMainUi);
|
||||
failure = CaptureFailure(failure, () => Interface.LanguageChanged -= LanguageChanged);
|
||||
failure = CaptureFailure(failure, () => Interface.UiBuilder.Draw -= Draw);
|
||||
failure = CaptureFailure(failure, () => Framework.Update -= FrameworkUpdate);
|
||||
@@ -486,44 +529,18 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
}
|
||||
);
|
||||
|
||||
// Unsubscribe AutoTellTabs before MessageManager goes away.
|
||||
failure = CaptureFailure(failure, () => AutoTellTabsService?.Dispose());
|
||||
|
||||
// MessageManager has its own async dispose path (DB flush, thread shutdown).
|
||||
if (MessageManager is not null)
|
||||
{
|
||||
failure = await CaptureFailureAsync(
|
||||
failure,
|
||||
() => MessageManager.DisposeAsync().AsTask()
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Game-function / IPC / window cleanup must run on the framework thread.
|
||||
// Framework-thread cleanup the container does not reach.
|
||||
try
|
||||
{
|
||||
await Framework
|
||||
.RunOnFrameworkThread(() =>
|
||||
{
|
||||
failure = CaptureFailure(failure, TearDownCommands);
|
||||
failure = CaptureFailure(
|
||||
failure,
|
||||
() => GameFunctions.GameFunctions.SetChatInteractable(true)
|
||||
);
|
||||
|
||||
// IPC subscribers before windows — prevents a final IPC event
|
||||
// from reaching a half-torn ChatLogWindow.
|
||||
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
|
||||
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
|
||||
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
|
||||
failure = CaptureFailure(failure, () => Ipc?.Dispose());
|
||||
|
||||
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
|
||||
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
|
||||
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
|
||||
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
|
||||
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
|
||||
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
|
||||
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
@@ -532,11 +549,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
failure ??= ex;
|
||||
}
|
||||
|
||||
// Pure-memory cleanups — no Framework / UI / IPC touch.
|
||||
failure = CaptureFailure(failure, () => Functions?.Dispose());
|
||||
failure = CaptureFailure(failure, () => Commands?.Dispose());
|
||||
// Container disposes services + windows on the framework thread.
|
||||
// MessageManager.DisposeAsync is not idempotent, so we let the
|
||||
// container do it once instead of double-disposing.
|
||||
if (_lifecycle is not null)
|
||||
{
|
||||
failure = await CaptureFailureAsync(failure, () => _lifecycle.DisposeAsync().AsTask())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Static-class cleanups the container has no handle on.
|
||||
failure = CaptureFailure(failure, () => EmoteCache.Dispose());
|
||||
// Static input history would otherwise survive the plugin reload.
|
||||
failure = CaptureFailure(failure, InputHistoryService.Reset);
|
||||
|
||||
if (failure is not null)
|
||||
@@ -683,11 +706,95 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenMainUi()
|
||||
// Central slash-command + UiBuilder.OpenConfigUi/OpenMainUi subscribe so
|
||||
// the four lazy windows (Settings, DbViewer, SeStringDebugger, Debugger)
|
||||
// have working entry points before they're constructed.
|
||||
private void SetupCommands()
|
||||
{
|
||||
SettingsWindow.IsOpen = !SettingsWindow.IsOpen;
|
||||
// ChatLogWindow.cs:128 already registers /hellion (ToggleChat). The
|
||||
// description-arg here keeps the Dalamud help list populated.
|
||||
_hellionSettingsCmd = Commands.Register(
|
||||
"/hellion",
|
||||
"Perform various actions with Hellion Chat."
|
||||
);
|
||||
_hellionSettingsCmd.Execute += OnHellionSettingsCommand;
|
||||
|
||||
_hellionViewCmd = Commands.Register(
|
||||
"/hellionView",
|
||||
"Get access to your message history, with simple filter options.",
|
||||
true
|
||||
);
|
||||
_hellionViewCmd.Execute += OnHellionViewCommand;
|
||||
|
||||
_hellionDebuggerCmd = Commands.Register("/hellionDebugger", showInHelp: false);
|
||||
_hellionDebuggerCmd.Execute += OnHellionDebuggerCommand;
|
||||
#if DEBUG
|
||||
// SeStringDebugger.cs lives under #if DEBUG too; keep this out of release builds.
|
||||
_hellionSeStringCmd = Commands.Register("/hellionSeString", showInHelp: false);
|
||||
_hellionSeStringCmd.Execute += OnHellionSeStringCommand;
|
||||
#endif
|
||||
|
||||
// Plugin-Manager "Settings" button. Was in Settings.cs:67 pre-v1.4.9.
|
||||
Interface.UiBuilder.OpenConfigUi += OnOpenConfigUi;
|
||||
|
||||
// Plugin-Manager "Open" button. Was in Plugin.cs LoadAsync pre-v1.4.9
|
||||
// (separate OpenMainUi handler that flipped SettingsWindow.IsOpen).
|
||||
Interface.UiBuilder.OpenMainUi += OnOpenMainUi;
|
||||
}
|
||||
|
||||
private void TearDownCommands()
|
||||
{
|
||||
Interface.UiBuilder.OpenMainUi -= OnOpenMainUi;
|
||||
Interface.UiBuilder.OpenConfigUi -= OnOpenConfigUi;
|
||||
|
||||
// Null-tolerant detaches: TearDownCommands can run from the LoadAsync
|
||||
// failure path (Plugin.cs CaptureFailure) before SetupCommands finished.
|
||||
if (_hellionSettingsCmd is not null)
|
||||
{
|
||||
_hellionSettingsCmd.Execute -= OnHellionSettingsCommand;
|
||||
_hellionSettingsCmd = null;
|
||||
}
|
||||
|
||||
if (_hellionViewCmd is not null)
|
||||
{
|
||||
_hellionViewCmd.Execute -= OnHellionViewCommand;
|
||||
_hellionViewCmd = null;
|
||||
}
|
||||
|
||||
if (_hellionDebuggerCmd is not null)
|
||||
{
|
||||
_hellionDebuggerCmd.Execute -= OnHellionDebuggerCommand;
|
||||
_hellionDebuggerCmd = null;
|
||||
}
|
||||
#if DEBUG
|
||||
if (_hellionSeStringCmd is not null)
|
||||
{
|
||||
_hellionSeStringCmd.Execute -= OnHellionSeStringCommand;
|
||||
_hellionSeStringCmd = null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnHellionSettingsCommand(string command, string arguments)
|
||||
{
|
||||
// /hellion with args is intentionally a no-op (matches pre-v1.4.9
|
||||
// Settings.cs:76-80 behaviour).
|
||||
if (string.IsNullOrWhiteSpace(arguments))
|
||||
SettingsWindow.Toggle();
|
||||
}
|
||||
|
||||
private void OnOpenConfigUi() => SettingsWindow.Toggle();
|
||||
|
||||
private void OnOpenMainUi() => SettingsWindow.Toggle();
|
||||
|
||||
private void OnHellionViewCommand(string _, string __) => DbViewer.Toggle();
|
||||
|
||||
private void OnHellionDebuggerCommand(string _, string __) => DebuggerWindow.Toggle();
|
||||
|
||||
#if DEBUG
|
||||
private void OnHellionSeStringCommand(string _, string __) => SeStringDebugger.Toggle();
|
||||
#endif
|
||||
|
||||
private void RunRetentionSweepIfDue()
|
||||
{
|
||||
if (!Config.RetentionEnabled)
|
||||
@@ -810,7 +917,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden;
|
||||
ChatLogWindow.DefaultText = ImGui.GetStyle().Colors[(int)ImGuiCol.Text];
|
||||
|
||||
using ((Config.FontsEnabled ? FontManager.RegularFont : FontManager.Axis).Push())
|
||||
// RegularFont is nullable only because the live rebuild path
|
||||
// disposes it before reassigning; both ends of that swap happen on
|
||||
// this same draw thread, so it cannot be null here.
|
||||
using ((Config.FontsEnabled ? FontManager.RegularFont! : FontManager.Axis).Push())
|
||||
WindowSystem.Draw();
|
||||
|
||||
ChatLogWindow.FinalizeFrame();
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using HellionChat.Infrastructure.Hosting;
|
||||
using HellionChat.Infrastructure.Logging;
|
||||
using HellionChat.Ipc;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Builds the generic-host DI container that drives v1.5.0+. The factory is
|
||||
// invoked synchronously from Plugin.ctor (after the schema gate clears) so the
|
||||
// container exists before PluginLifecycle.LoadAsync runs. See plan §1 for the
|
||||
// deliberate divergence from Lightless' deferred Func-delegate pattern.
|
||||
internal static class PluginHostFactory
|
||||
{
|
||||
public static IHost Build(Plugin plugin, PluginHostDependencies dependencies)
|
||||
{
|
||||
return new HostBuilder()
|
||||
.UseContentRoot(dependencies.PluginInterface.ConfigDirectory.FullName)
|
||||
.ConfigureLogging(logging =>
|
||||
{
|
||||
logging.ClearProviders();
|
||||
logging.AddDalamudLogging(dependencies.PluginLog);
|
||||
logging.SetMinimumLevel(LogLevel.Trace);
|
||||
})
|
||||
.ConfigureServices(services => ConfigureServices(services, plugin, dependencies))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private static void ConfigureServices(
|
||||
IServiceCollection services,
|
||||
Plugin plugin,
|
||||
PluginHostDependencies dependencies
|
||||
)
|
||||
{
|
||||
// Block A — Dalamud services (21 [PluginService] singletons).
|
||||
services.AddSingleton(dependencies);
|
||||
services.AddSingleton(dependencies.PluginInterface);
|
||||
services.AddSingleton(dependencies.PluginLog);
|
||||
services.AddSingleton(dependencies.ChatGui);
|
||||
services.AddSingleton(dependencies.ClientState);
|
||||
services.AddSingleton(dependencies.CommandManager);
|
||||
services.AddSingleton(dependencies.Condition);
|
||||
services.AddSingleton(dependencies.DataManager);
|
||||
services.AddSingleton(dependencies.Framework);
|
||||
services.AddSingleton(dependencies.GameGui);
|
||||
services.AddSingleton(dependencies.KeyState);
|
||||
services.AddSingleton(dependencies.ObjectTable);
|
||||
services.AddSingleton(dependencies.PartyList);
|
||||
services.AddSingleton(dependencies.TargetManager);
|
||||
services.AddSingleton(dependencies.TextureProvider);
|
||||
services.AddSingleton(dependencies.GameInteropProvider);
|
||||
services.AddSingleton(dependencies.GameConfig);
|
||||
services.AddSingleton(dependencies.Notification);
|
||||
services.AddSingleton(dependencies.AddonLifecycle);
|
||||
services.AddSingleton(dependencies.PlayerState);
|
||||
services.AddSingleton(dependencies.Evaluator);
|
||||
services.AddSingleton(dependencies.SelfTestRegistry);
|
||||
|
||||
// Self-references: Plugin and its WindowSystem already exist.
|
||||
services.AddSingleton(plugin);
|
||||
services.AddSingleton(plugin.WindowSystem);
|
||||
services.AddSingleton<PluginLifecycle>();
|
||||
|
||||
// Block B — HellionChat singletons. Factory lambdas because most
|
||||
// classes are internal-sealed and the default activator only sees
|
||||
// public ctors.
|
||||
services.AddSingleton<IPlatformUtil>(_ => new DalamudPlatformUtil());
|
||||
services.AddSingleton<IPluginLogProxy>(sp => new DalamudPluginLogProxy(
|
||||
sp.GetRequiredService<IPluginLog>()
|
||||
));
|
||||
services.AddSingleton<FileDialogManager>(_ => new FileDialogManager());
|
||||
services.AddSingleton(sp => new Commands(sp.GetRequiredService<ILogger<Commands>>()));
|
||||
services.AddSingleton(sp => new FontManager(
|
||||
sp.GetRequiredService<IDalamudPluginInterface>()
|
||||
));
|
||||
services.AddSingleton(_ => new StatusBar());
|
||||
services.AddSingleton(sp => new IpcManager(sp.GetRequiredService<ILogger<IpcManager>>()));
|
||||
services.AddSingleton(sp => new ExtraChat(sp.GetRequiredService<ILogger<ExtraChat>>()));
|
||||
|
||||
services.AddSingleton(sp => new ThemeRegistry(
|
||||
Path.Combine(
|
||||
sp.GetRequiredService<IDalamudPluginInterface>().ConfigDirectory.FullName,
|
||||
"themes"
|
||||
),
|
||||
sp.GetRequiredService<ILogger<ThemeRegistry>>()
|
||||
));
|
||||
|
||||
services.AddSingleton(sp => new GameFunctions.GameFunctions(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILogger<GameFunctions.GameFunctions>>(),
|
||||
sp.GetRequiredService<ILoggerFactory>()
|
||||
));
|
||||
services.AddSingleton(sp => new TypingIpc(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILogger<TypingIpc>>()
|
||||
));
|
||||
|
||||
services.AddSingleton(sp => new Integrations.HonorificService(
|
||||
sp.GetRequiredService<IDalamudPluginInterface>(),
|
||||
sp.GetRequiredService<ILogger<Integrations.HonorificService>>(),
|
||||
sp.GetRequiredService<IFramework>()
|
||||
));
|
||||
|
||||
services.AddSingleton(sp => new MessageManager(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILogger<MessageManager>>(),
|
||||
sp.GetRequiredService<ILoggerFactory>()
|
||||
));
|
||||
|
||||
// MessageStore is allocated inside MessageManager.ctor; a separate
|
||||
// container singleton would double-construct the SQLite handle.
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var pluginRef = sp.GetRequiredService<Plugin>();
|
||||
var manager = sp.GetRequiredService<MessageManager>();
|
||||
return new AutoTellTabsService(
|
||||
pluginRef,
|
||||
manager,
|
||||
manager.Store,
|
||||
sp.GetRequiredService<ILogger<AutoTellTabsService>>()
|
||||
);
|
||||
});
|
||||
|
||||
// Block C — Windows. WindowSystem.AddWindow is called from
|
||||
// PluginLifecycle.LoadAsync on the framework thread.
|
||||
services.AddSingleton(sp => new ChatLogWindow(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILogger<ChatLogWindow>>(),
|
||||
sp.GetRequiredService<ILoggerFactory>()
|
||||
));
|
||||
services.AddSingleton(sp => new SettingsWindow(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILoggerFactory>()
|
||||
));
|
||||
services.AddSingleton(sp => new DbViewer(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
sp.GetRequiredService<ILogger<DbViewer>>()
|
||||
));
|
||||
services.AddSingleton(sp => new InputPreview(sp.GetRequiredService<ChatLogWindow>()));
|
||||
services.AddSingleton(sp => new CommandHelpWindow(sp.GetRequiredService<ChatLogWindow>()));
|
||||
services.AddSingleton(sp => new SeStringDebugger(sp.GetRequiredService<Plugin>()));
|
||||
services.AddSingleton(sp => new DebuggerWindow(sp.GetRequiredService<Plugin>()));
|
||||
services.AddSingleton(sp => new FirstRunWizard(sp.GetRequiredService<Plugin>()));
|
||||
|
||||
// Hosted-service adapters: thin wrappers around the existing init
|
||||
// methods so the service class bodies stay unchanged. FontManager
|
||||
// does not need one — its ctor runs the init inline inside a single
|
||||
// SuppressAutoRebuild block on eager resolve.
|
||||
services.AddHostedService(sp => new ThemeRegistryInitHostedService(
|
||||
sp.GetRequiredService<ThemeRegistry>()
|
||||
));
|
||||
services.AddHostedService(sp => new IpcManagerInitHostedService(
|
||||
sp.GetRequiredService<IpcManager>()
|
||||
));
|
||||
services.AddHostedService(sp => new TypingIpcInitHostedService(
|
||||
sp.GetRequiredService<TypingIpc>()
|
||||
));
|
||||
services.AddHostedService(sp => new ExtraChatInitHostedService(
|
||||
sp.GetRequiredService<ExtraChat>()
|
||||
));
|
||||
services.AddHostedService(sp => new MessageManagerInitHostedService(
|
||||
sp.GetRequiredService<IDalamudPluginInterface>(),
|
||||
sp.GetRequiredService<MessageManager>()
|
||||
));
|
||||
services.AddHostedService(sp => new AutoTellTabsServiceInitHostedService(
|
||||
sp.GetRequiredService<AutoTellTabsService>()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record PluginHostDependencies(
|
||||
IDalamudPluginInterface PluginInterface,
|
||||
IPluginLog PluginLog,
|
||||
IChatGui ChatGui,
|
||||
IClientState ClientState,
|
||||
ICommandManager CommandManager,
|
||||
ICondition Condition,
|
||||
IDataManager DataManager,
|
||||
IFramework Framework,
|
||||
IGameGui GameGui,
|
||||
IKeyState KeyState,
|
||||
IObjectTable ObjectTable,
|
||||
IPartyList PartyList,
|
||||
ITargetManager TargetManager,
|
||||
ITextureProvider TextureProvider,
|
||||
IGameInteropProvider GameInteropProvider,
|
||||
IGameConfig GameConfig,
|
||||
INotificationManager Notification,
|
||||
IAddonLifecycle AddonLifecycle,
|
||||
IPlayerState PlayerState,
|
||||
ISeStringEvaluator Evaluator,
|
||||
ISelfTestRegistry SelfTestRegistry
|
||||
);
|
||||
@@ -0,0 +1,143 @@
|
||||
using System.Runtime.ExceptionServices;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Orchestrates Host.StartAsync / StopAsync and the framework-thread dispose.
|
||||
// Plugin.ctor builds the host and assigns it via the Host property, so
|
||||
// PluginLifecycle never constructs the host itself.
|
||||
internal sealed class PluginLifecycle : IAsyncDisposable
|
||||
{
|
||||
private readonly IFramework _framework;
|
||||
private readonly Plugin _plugin;
|
||||
|
||||
private int _disposeStarted;
|
||||
private bool _hostStartRequested;
|
||||
|
||||
public PluginLifecycle(IFramework framework, Plugin plugin)
|
||||
{
|
||||
_framework = framework;
|
||||
_plugin = plugin;
|
||||
}
|
||||
|
||||
// Plugin.ctor fills this immediately after PluginHostFactory.Build and
|
||||
// before invoking LoadAsync; LoadAsync may NRE-suppress on Host! safely.
|
||||
public IHost? Host { get; set; }
|
||||
|
||||
public async Task LoadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
_hostStartRequested = true;
|
||||
await Host!.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// WindowSystem.AddWindow mutates an internal List<>; v1.4.9 Stage-2
|
||||
// verified the list is non-thread-safe, so we marshal the entire
|
||||
// registration block to the framework thread.
|
||||
await _framework
|
||||
.RunOnFrameworkThread(() => RegisterWindows(_plugin))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
await DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow secondary dispose failure so the original load throw wins.
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterWindows(Plugin plugin)
|
||||
{
|
||||
plugin.WindowSystem.AddWindow(plugin.ChatLogWindow);
|
||||
plugin.WindowSystem.AddWindow(plugin.SettingsWindow);
|
||||
plugin.WindowSystem.AddWindow(plugin.DbViewer);
|
||||
plugin.WindowSystem.AddWindow(plugin.InputPreview);
|
||||
plugin.WindowSystem.AddWindow(plugin.CommandHelpWindow);
|
||||
plugin.WindowSystem.AddWindow(plugin.SeStringDebugger);
|
||||
plugin.WindowSystem.AddWindow(plugin.DebuggerWindow);
|
||||
plugin.WindowSystem.AddWindow(plugin.FirstRunWizard);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
// Idempotency guard — Dalamud may fire DisposeAsync twice in a reload race.
|
||||
if (Interlocked.Exchange(ref _disposeStarted, 1) != 0)
|
||||
return;
|
||||
|
||||
Exception? failure = null;
|
||||
|
||||
if (_hostStartRequested && Host is not null)
|
||||
failure = await CaptureFailureAsync(failure, () => Host.StopAsync())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
failure = await DisposeHostOnFrameworkThreadAsync(failure).ConfigureAwait(false);
|
||||
|
||||
ThrowIfFailed(failure);
|
||||
}
|
||||
|
||||
private async Task<Exception?> DisposeHostOnFrameworkThreadAsync(Exception? failure)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _framework
|
||||
.RunOnFrameworkThread(() =>
|
||||
{
|
||||
failure = CaptureFailure(failure, () => Host?.Dispose());
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure ??= ex;
|
||||
}
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
private static Exception? CaptureFailure(Exception? failure, Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure ??= ex;
|
||||
}
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
private static async ValueTask<Exception?> CaptureFailureAsync(
|
||||
Exception? failure,
|
||||
Func<Task> action
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failure ??= ex;
|
||||
}
|
||||
|
||||
return failure;
|
||||
}
|
||||
|
||||
private static void ThrowIfFailed(Exception? failure)
|
||||
{
|
||||
if (failure is not null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
.:;+xXXX$$$$$$$$XXx+;:
|
||||
.X$+ .;+X$$$$$$$$$$$$$$$$$$$$$$$$$$$x:
|
||||
;$xx$$X+:... .....::+X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$;.
|
||||
X$; .:+xXXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X:
|
||||
$$; :++xX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X;
|
||||
$$x. .+$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X.
|
||||
x$$; ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+;::::::;x$$$$$:
|
||||
:$$$; .+$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+:. .+$$$$$$$$$X+;;:
|
||||
;$$$+. :X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X;: :$$$$$$$$$$$$$$$$X;.
|
||||
.+$$$X: ..;X$$$$$$$$$$$$$$$$$$$$$$$$$$X;.. :$$$$$$$$$$$$$$$$$$$$X:
|
||||
;$$$$$X+::::+X$$$$$$$$$$$$$$$$$$$$$X;. .$$$$$$$$$$$$$$$$$$$$$$$X;
|
||||
+$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+: Hellion Forge x$$$$$$$$$$$$$$$$$$$$$$$$$X:
|
||||
.;x$$$$$$$$$$$$$$$$$$$$$x;: .X$$$$$$$$$$$$$$$$$$$$$$$$$$$+
|
||||
.;+$$$$$$$$$$X+;:.. .X$$$$$$$$$$$$$$$$$$$$$$$$$$$$+
|
||||
.X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$;
|
||||
.X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
;$$$$$$xx$$$$$$$$$$$$$$$$$$$$$x
|
||||
.$$$$$$x+$$$$$$$$$$$$$$$$$$$$$x
|
||||
:+X$$$$$$X;$$$$$$$$$$$$$$$$$$$$$$:
|
||||
;$$$$$$$$$$;$$$$$$$$$$$$$$$$$$$$$$X.
|
||||
+$$$$$$$$$$;x$$$$$$$$$$$$$$$$$$$$$$+
|
||||
x$$$$$$$$$$:$$$$$$$$$$$$$$$$$$$$$$X:
|
||||
.X$$$$$$$$$.:$$$$$$$$$$$$$$$$$$$$$$;
|
||||
:X$$X;;;;: .$$$$$$$$$$$$$$$$$$$$$$X.
|
||||
.$$$$X .$$$$$$$$$$$$$$$$$$$$$$$:
|
||||
.$$$$+ .X$$$$$$$$$$$$$$$$$$$$$$;
|
||||
;$$$$: .X$$$$$$$$$$$$$$$$$$$$$$x
|
||||
:X$$$+ .$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
+$$$x :$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
;$$X: $$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
x$$$$$$$$$$$$$$$$$$$$$$$$X
|
||||
+$$$$$$$$$$$$$$$$$$$$$$$$$+
|
||||
.+$$$$$$$$$$$$$$$$$$$$$$$$$$;
|
||||
. ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$:
|
||||
:X$x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
|
||||
.XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$+
|
||||
;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$+$;
|
||||
.. ++X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$:+$:
|
||||
:$$+. ;$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$;:$$+
|
||||
.x+X$X: X$$$$$$$$$$x::;:;$$$$$$$$$$$$$$$$$$X: ;$X.
|
||||
:X.x$$$:.::::::;x+:X$$$$;$$$$$$$$$$$$$$$$$$: :X;
|
||||
:x.x$$$$$$$$$$$$$$$$$;;$:$$$$$$$$$$$$$$$$$: :$+
|
||||
:Xx$$$$$$$$$$$$$$$$$: ;X;$$$$$$$$$$$$$$$$: .+$$;
|
||||
;$$$$$$$$$$$$$$$$$$; .X+X$$$$$$$$$$$$$$$+ .+$+.
|
||||
+$$$$$$$$$$$$$$$$$$$$$$$;+$$$$$$$$$$$$$$X: .+X:
|
||||
+$$$$$$$$$$$$$$$$$$$$$$$$$+:$$$$$$$$$$$$$+.+$+.
|
||||
;$$$$$$$$$$$$$$$$$$$$$$$$$$$X;$$$$$$$$$$$$$$X:
|
||||
+X: .:X$$$$$$$$x+++x$$$$$$$$;:X$$$$$$$$$$$X:
|
||||
:x.;$;+$$$$$:. :X$$$$X :$$$$$$$$$$X:
|
||||
;x :X$$$; .x$$x X$$; .:+.$$$$$$$$$$x
|
||||
xx.X$$X: X$;.:$X:.X$$$$$$$$$:
|
||||
+$$$$X. ;$;::: .$$$$$$$$$:
|
||||
;$$$; :+X$$$$XX$; X$$$$$$$$:
|
||||
;$$X: .:x$x$$$$$X. x$$$$$$$$:
|
||||
:X$X: :+x; :$$$$$: +$$$$$$$X:
|
||||
:++$X+xXX;. +$$$$. +$$$$$$$+.
|
||||
... .X$$$X. +$$$$$$$:
|
||||
;$$$$; .X$$$$$$x.
|
||||
;$$X; :X$$$$$$;
|
||||
;$$$$$$x.
|
||||
.X$$$$$$;
|
||||
;$$$$$$+
|
||||
+$$$$$;
|
||||
:X$$$$;.
|
||||
;$$$$+.
|
||||
.x$$$X:
|
||||
.+$$X;
|
||||
@@ -0,0 +1,4 @@
|
||||
|\_/|,,_____,~~`
|
||||
(.".)~~ )`~}}
|
||||
\o/\ /---~\\ ~}}
|
||||
_// _// ~}
|
||||
@@ -270,6 +270,10 @@ internal class HellionStrings
|
||||
internal static string Settings_Chat_Preview_Heading => Get(nameof(Settings_Chat_Preview_Heading));
|
||||
internal static string Settings_Chat_Emotes_Heading => Get(nameof(Settings_Chat_Emotes_Heading));
|
||||
|
||||
// Hellion Chat — Chat-Tab SymbolPicker
|
||||
internal static string Settings_Chat_SymbolPicker_Enable_Name => Get(nameof(Settings_Chat_SymbolPicker_Enable_Name));
|
||||
internal static string Settings_Chat_SymbolPicker_Enable_Description => Get(nameof(Settings_Chat_SymbolPicker_Enable_Description));
|
||||
|
||||
// Hellion Chat — Database-Tab section headings
|
||||
internal static string Settings_Database_Storage_Heading => Get(nameof(Settings_Database_Storage_Heading));
|
||||
internal static string Settings_Database_Viewer_Heading => Get(nameof(Settings_Database_Viewer_Heading));
|
||||
|
||||
@@ -556,6 +556,14 @@
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat-Tab SymbolPicker -->
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Name" xml:space="preserve">
|
||||
<value>Symbol-Picker-Button neben dem Chat-Eingang anzeigen</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Description" xml:space="preserve">
|
||||
<value>Fügt einen kleinen Button links neben dem Kanal-Indikator ein. Klick öffnet ein Popup mit FFXIV-Glyphen und einer kuratierten Symbol-Liste. Ausschalten für eine schlankere Eingabezeile.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Database-Tabs -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
|
||||
@@ -556,6 +556,14 @@
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat tab SymbolPicker -->
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Name" xml:space="preserve">
|
||||
<value>Show symbol-picker button next to chat input</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Description" xml:space="preserve">
|
||||
<value>Adds a small button left of the channel indicator that opens a popup with FFXIV icons and a curated symbol list. Disable if you prefer a leaner input bar.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Database tab section headings -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Storage</value>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Verifies the FontManager came out of the DI container with every
|
||||
// handle attached and no atlas-load failure on any of them. ItalicFont
|
||||
// is allowed to be null when ItalicEnabled is off.
|
||||
internal sealed class FontManagerCtorSmokeStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
public FontManagerCtorSmokeStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - FontManager ctor smoke";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var fm = this.plugin.FontManager;
|
||||
if (fm is null)
|
||||
{
|
||||
ImGui.Text("Plugin.FontManager is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.Axis is null)
|
||||
{
|
||||
ImGui.Text("Axis handle is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.AxisItalic is null)
|
||||
{
|
||||
ImGui.Text("AxisItalic handle is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.FontAwesome is null)
|
||||
{
|
||||
ImGui.Text("FontAwesome handle is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.RegularFont is null)
|
||||
{
|
||||
ImGui.Text("RegularFont handle is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (Plugin.Config.ItalicEnabled && fm.ItalicFont is null)
|
||||
{
|
||||
ImGui.Text("ItalicEnabled is on but ItalicFont handle is null");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.Axis.LoadException is { } e1)
|
||||
{
|
||||
ImGui.Text($"Axis load exception: {e1.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.AxisItalic.LoadException is { } e2)
|
||||
{
|
||||
ImGui.Text($"AxisItalic load exception: {e2.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.FontAwesome.LoadException is { } e3)
|
||||
{
|
||||
ImGui.Text($"FontAwesome load exception: {e3.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.RegularFont.LoadException is { } e4)
|
||||
{
|
||||
ImGui.Text($"RegularFont load exception: {e4.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
if (fm.ItalicFont?.LoadException is { } e5)
|
||||
{
|
||||
ImGui.Text($"ItalicFont load exception: {e5.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp() { }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Push-safety smoke: IFontHandle.Push() is contracted safe regardless
|
||||
// of the Available state, so this step proves the ctor-built handles
|
||||
// can be pushed even right after plugin load. Self-test steps run on
|
||||
// the framework thread via the xlperf path, so the push call itself
|
||||
// stays main-thread-safe.
|
||||
internal sealed class FontPushSmokeStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
public FontPushSmokeStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - FontManager push smoke";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var fm = this.plugin.FontManager;
|
||||
if (fm?.RegularFont is null || fm.FontAwesome is null)
|
||||
{
|
||||
ImGui.Text("RegularFont or FontAwesome missing - see FontManager ctor smoke");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (fm.RegularFont.Push()) { }
|
||||
using (fm.FontAwesome.Push()) { }
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ImGui.Text($"Push threw: {e.GetType().Name}: {e.Message}");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp() { }
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
using HellionChat.Themes.Builtin;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Themes;
|
||||
|
||||
public sealed class ThemeRegistry
|
||||
{
|
||||
private readonly ILogger<ThemeRegistry>? _logger;
|
||||
|
||||
public const string DefaultSlug = HellionArctic.Slug;
|
||||
|
||||
// 1Hz throttle for the v1.4.8 B2 auto-refresh-on-active path. The
|
||||
@@ -29,8 +32,9 @@ public sealed class ThemeRegistry
|
||||
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
|
||||
private DateTime _lastActiveStamp = DateTime.MinValue;
|
||||
|
||||
public ThemeRegistry(string? customThemesDir = null)
|
||||
public ThemeRegistry(string? customThemesDir = null, ILogger<ThemeRegistry>? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
// Insertion order drives the Theme-Picker grid layout (3 columns).
|
||||
// Row 1: blue family. Row 2: purple to magenta family.
|
||||
// Row 3: green / warm / classic. Row 4: Synthwave Sunset as a
|
||||
@@ -206,7 +210,7 @@ public sealed class ThemeRegistry
|
||||
catch (Exception ex) when (IsRecoverableFileLock(ex))
|
||||
{
|
||||
// Editor mid-save: keep last known good, retry on next refresh.
|
||||
Plugin.LogProxy.Debug(
|
||||
_logger?.LogDebug(
|
||||
$"Custom theme {Path.GetFileName(path)} is locked, keeping last known good"
|
||||
);
|
||||
if (cached.Theme is not null)
|
||||
|
||||
+148
-36
@@ -22,6 +22,7 @@ using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
@@ -40,6 +41,7 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
private readonly CommandWrapper _clearHellionCommand;
|
||||
private readonly CommandWrapper _hellionCommand;
|
||||
private readonly SymbolPicker _symbolPicker;
|
||||
|
||||
internal bool ScreenshotMode;
|
||||
private string Salt { get; }
|
||||
@@ -97,10 +99,19 @@ public sealed class ChatLogWindow : Window
|
||||
private long FrameTime; // set every frame
|
||||
internal long LastActivityTime = Environment.TickCount64;
|
||||
|
||||
internal ChatLogWindow(Plugin plugin)
|
||||
private readonly ILogger<ChatLogWindow> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
internal ChatLogWindow(
|
||||
Plugin plugin,
|
||||
ILogger<ChatLogWindow> logger,
|
||||
ILoggerFactory loggerFactory
|
||||
)
|
||||
: base($"{Plugin.PluginName}###chat2")
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
Salt = new Random().Next().ToString();
|
||||
|
||||
Size = new Vector2(500, 250);
|
||||
@@ -113,8 +124,10 @@ public sealed class ChatLogWindow : Window
|
||||
DisableWindowSounds = true;
|
||||
// AllowBackgroundBlur is set centrally in Plugin.Setup after AddWindow.
|
||||
|
||||
PayloadHandler = new PayloadHandler(this);
|
||||
HandlerLender = new Lender<PayloadHandler>(() => new PayloadHandler(this));
|
||||
PayloadHandler = new PayloadHandler(this, _loggerFactory.CreateLogger<PayloadHandler>());
|
||||
HandlerLender = new Lender<PayloadHandler>(() =>
|
||||
new PayloadHandler(this, _loggerFactory.CreateLogger<PayloadHandler>())
|
||||
);
|
||||
|
||||
SetUpTextCommandChannels();
|
||||
SetUpAllCommands();
|
||||
@@ -129,6 +142,8 @@ public sealed class ChatLogWindow : Window
|
||||
_clearHellionCommand.Execute += ClearLog;
|
||||
_hellionCommand.Execute += ToggleChat;
|
||||
|
||||
_symbolPicker = new SymbolPicker();
|
||||
|
||||
Plugin.ClientState.Login += Login;
|
||||
Plugin.ClientState.Logout += Logout;
|
||||
|
||||
@@ -189,11 +204,28 @@ public sealed class ChatLogWindow : Window
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Cherry-picked from ChatTwo upstream ee7768ac (Infiziert90, 2026-05-16)
|
||||
// - Replace the chat input when args.AddIfNotPresent / args.Input starts
|
||||
// with a slash. Vanilla actions like the Friend List "/tell" entry and
|
||||
// other plugins push slash commands through these args; appending them
|
||||
// to existing text would produce inputs like "test/tell user@world".
|
||||
// ---------------------------------------------------------------
|
||||
if (args.AddIfNotPresent != null && !Chat.Contains(args.AddIfNotPresent))
|
||||
Chat += args.AddIfNotPresent;
|
||||
{
|
||||
if (args.AddIfNotPresent.StartsWith('/'))
|
||||
Chat = args.AddIfNotPresent;
|
||||
else
|
||||
Chat += args.AddIfNotPresent;
|
||||
}
|
||||
|
||||
if (args.Input != null)
|
||||
Chat += args.Input;
|
||||
{
|
||||
if (args.Input.StartsWith('/'))
|
||||
Chat = args.Input;
|
||||
else
|
||||
Chat += args.Input;
|
||||
}
|
||||
|
||||
var (info, reason, target) = (args.ChannelSwitchInfo, args.TellReason, args.TellTarget);
|
||||
|
||||
@@ -277,7 +309,7 @@ public sealed class ChatLogWindow : Window
|
||||
|| !GameFunctions.Chat.IsChannelOrExistingLinkshell(targetChannel.Value)
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
$"Channel was set to an invalid value '{targetChannel}', ignoring"
|
||||
);
|
||||
return;
|
||||
@@ -331,11 +363,11 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
case "hide":
|
||||
CurrentHideState = HideState.User;
|
||||
Plugin.LogProxy.Verbose("HideState: → User (chat hide command)");
|
||||
_logger.LogTrace("HideState: → User (chat hide command)");
|
||||
break;
|
||||
case "show":
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.LogProxy.Verbose("HideState: → None (chat show command)");
|
||||
_logger.LogTrace("HideState: → None (chat show command)");
|
||||
break;
|
||||
case "toggle":
|
||||
CurrentHideState = CurrentHideState switch
|
||||
@@ -345,7 +377,7 @@ public sealed class ChatLogWindow : Window
|
||||
HideState.None => HideState.User,
|
||||
_ => CurrentHideState,
|
||||
};
|
||||
Plugin.LogProxy.Verbose($"HideState: → {CurrentHideState} (chat toggle command)");
|
||||
_logger.LogTrace($"HideState: → {CurrentHideState} (chat toggle command)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -455,7 +487,7 @@ public sealed class ChatLogWindow : Window
|
||||
else if (newTab.CurrentChannel.Channel is InputChannel.Invalid)
|
||||
{
|
||||
newTab.CurrentChannel = previousTab.CurrentChannel.Clone();
|
||||
Plugin.LogProxy.Debug(
|
||||
_logger.LogDebug(
|
||||
$"[Tab] '{newTab.Name}' seeded channel from '{previousTab.Name}' "
|
||||
+ $"(Channel={newTab.CurrentChannel.Channel}, TellTarget={newTab.CurrentChannel.TellTarget?.ToTargetString() ?? "null"})"
|
||||
);
|
||||
@@ -483,14 +515,14 @@ public sealed class ChatLogWindow : Window
|
||||
if (Plugin.Config.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.Battle;
|
||||
Plugin.LogProxy.Verbose("HideState: None → Battle");
|
||||
_logger.LogTrace("HideState: None → Battle");
|
||||
}
|
||||
|
||||
// If the chat is hidden because of battle, we reset it here
|
||||
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.LogProxy.Verbose("HideState: Battle → None");
|
||||
_logger.LogTrace("HideState: Battle → None");
|
||||
}
|
||||
|
||||
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
|
||||
@@ -503,7 +535,7 @@ public sealed class ChatLogWindow : Window
|
||||
if (Plugin.Functions.Chat.CheckHideFlags())
|
||||
{
|
||||
CurrentHideState = HideState.Cutscene;
|
||||
Plugin.LogProxy.Verbose("HideState: None → Cutscene");
|
||||
_logger.LogTrace("HideState: None → Cutscene");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,7 +546,7 @@ public sealed class ChatLogWindow : Window
|
||||
&& !Plugin.GposeActive
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Verbose($"HideState: {CurrentHideState} → None (cutscene/gpose ended)");
|
||||
_logger.LogTrace($"HideState: {CurrentHideState} → None (cutscene/gpose ended)");
|
||||
CurrentHideState = HideState.None;
|
||||
}
|
||||
|
||||
@@ -522,14 +554,14 @@ public sealed class ChatLogWindow : Window
|
||||
if (CurrentHideState == HideState.Cutscene && Activate)
|
||||
{
|
||||
CurrentHideState = HideState.CutsceneOverride;
|
||||
Plugin.LogProxy.Verbose("HideState: Cutscene → CutsceneOverride (user activate)");
|
||||
_logger.LogTrace("HideState: Cutscene → CutsceneOverride (user activate)");
|
||||
}
|
||||
|
||||
// if the user hid the chat and is now activating chat, reset the hide state
|
||||
if (CurrentHideState == HideState.User && Activate)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.LogProxy.Verbose("HideState: User → None (activate)");
|
||||
_logger.LogTrace("HideState: User → None (activate)");
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -636,6 +668,15 @@ public sealed class ChatLogWindow : Window
|
||||
IsOpen = true;
|
||||
}
|
||||
|
||||
// v1.4.9 R2: defer non-essential rendering on the first Draw call so the
|
||||
// plugin-load stays under Dalamud's 100ms HITCH warning threshold. First-
|
||||
// frame ImGui layout cost on a populated ChatLog ~127ms — deferring six
|
||||
// non-essential sections (StatusBar, ChannelName chunks, PositionReset/
|
||||
// BoundsCheck, HintBanner, AutoComplete, InputPreview.CalculatePreview)
|
||||
// shaves ~33ms down to ~94ms. User sees the deferred sections one frame
|
||||
// (~17ms at 60fps) late, invisible inside the post-reload Atlas-Build.
|
||||
private bool _firstFrameDone;
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
DrewThisFrame = true;
|
||||
@@ -643,11 +684,15 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
DrawChatLog();
|
||||
AddPopOutsToDraw();
|
||||
DrawAutoComplete();
|
||||
|
||||
// v1.4.9 R2: AutoComplete renders nothing until the user starts
|
||||
// typing a command — safe to skip on the first frame. ~6ms.
|
||||
if (_firstFrameDone)
|
||||
DrawAutoComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Error drawing Chat Log window");
|
||||
_logger.LogError(ex, "Error drawing Chat Log window");
|
||||
if (!NotifiedDrawFailure)
|
||||
{
|
||||
Plugin.Notification.AddNotification(
|
||||
@@ -665,6 +710,13 @@ public sealed class ChatLogWindow : Window
|
||||
// input focus, which breaks every other ImGui window.
|
||||
Activate = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Flag flips after the first Draw completes (success or caught
|
||||
// exception). Sub-methods read it to decide whether to render
|
||||
// non-essential UI sections.
|
||||
_firstFrameDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsChatMode =>
|
||||
@@ -680,18 +732,25 @@ public sealed class ChatLogWindow : Window
|
||||
LastWindowSize = currentSize;
|
||||
LastWindowPos = ImGui.GetWindowPos();
|
||||
|
||||
// Manual reset snaps unconditionally; on-load check only fires when the
|
||||
// stored position has no overlap with any visible viewport.
|
||||
if (RequestPositionReset)
|
||||
// v1.4.9 R2: skip the bounds-check chain on the first frame. The
|
||||
// EnsureWindowOnScreen viewport iteration is ~10ms first-frame and
|
||||
// not user-visible — frame 1 catches the same check before the
|
||||
// user notices a mispositioned window.
|
||||
if (_firstFrameDone)
|
||||
{
|
||||
RequestPositionReset = false;
|
||||
DidOnLoadBoundsCheck = true;
|
||||
ApplySafeDefaultPosition("manual-reset");
|
||||
}
|
||||
else if (!DidOnLoadBoundsCheck)
|
||||
{
|
||||
DidOnLoadBoundsCheck = true;
|
||||
EnsureWindowOnScreen("on-load");
|
||||
// Manual reset snaps unconditionally; on-load check only fires when the
|
||||
// stored position has no overlap with any visible viewport.
|
||||
if (RequestPositionReset)
|
||||
{
|
||||
RequestPositionReset = false;
|
||||
DidOnLoadBoundsCheck = true;
|
||||
ApplySafeDefaultPosition("manual-reset");
|
||||
}
|
||||
else if (!DidOnLoadBoundsCheck)
|
||||
{
|
||||
DidOnLoadBoundsCheck = true;
|
||||
EnsureWindowOnScreen("on-load");
|
||||
}
|
||||
}
|
||||
|
||||
if (resized)
|
||||
@@ -700,12 +759,17 @@ public sealed class ChatLogWindow : Window
|
||||
LastViewport = ImGui.GetWindowViewport().Handle;
|
||||
WasDocked = ImGui.IsWindowDocked();
|
||||
|
||||
if (IsChatMode && Plugin.InputPreview.IsDrawable)
|
||||
// v1.4.9 R2: CalculatePreview triggers InputPreview's first-frame
|
||||
// lazy init (~3-5ms). User-typing-driven, safe to defer one frame.
|
||||
if (_firstFrameDone && IsChatMode && Plugin.InputPreview.IsDrawable)
|
||||
Plugin.InputPreview.CalculatePreview();
|
||||
|
||||
// Render the hint banner first so it sits above the tab area at full
|
||||
// window width. ImGui accounts for its height automatically.
|
||||
DrawV061HintBannerIfNeeded();
|
||||
// v1.4.9 R2: skip on first frame (~3-5ms layout cost). The banner
|
||||
// is a v0.6.1 migration notice that returns the same result frame 1.
|
||||
if (_firstFrameDone)
|
||||
DrawV061HintBannerIfNeeded();
|
||||
|
||||
if (Plugin.Config.SidebarTabView)
|
||||
DrawTabSidebar();
|
||||
@@ -760,6 +824,40 @@ public sealed class ChatLogWindow : Window
|
||||
)
|
||||
inputColour = ecColour;
|
||||
|
||||
// Symbol-picker trigger sits left of the channel indicator. ImRaii.Popup
|
||||
// inside DrawAndConsume pins to the last rendered item, so the call MUST
|
||||
// run immediately after this IconButton — placing it after the channel
|
||||
// picker below would pin the popup under the wrong widget.
|
||||
if (Plugin.Config.SymbolPickerEnabled)
|
||||
{
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.Smile,
|
||||
"symbol-picker-trigger",
|
||||
"Insert symbol or FFXIV icon"
|
||||
)
|
||||
)
|
||||
{
|
||||
_symbolPicker.OpenPopup();
|
||||
}
|
||||
}
|
||||
// DrawAndConsume runs unconditionally; with the button hidden the popup
|
||||
// can't open, so the call is a no-op. Splice path stays outside the
|
||||
// guard for the same reason.
|
||||
var insertedSymbol = _symbolPicker.DrawAndConsume();
|
||||
if (insertedSymbol is not null)
|
||||
{
|
||||
// Same cursor-aware splice idiom as the AutoComplete commit path at
|
||||
// ChatLogWindow.cs:2487-2493. Clamp because CursorPos can drift if
|
||||
// the user mutates Chat while the popup is open.
|
||||
var pos = Math.Clamp(CursorPos, 0, Chat.Length);
|
||||
Chat = Chat[..pos] + insertedSymbol + Chat[pos..];
|
||||
Activate = true;
|
||||
ActivatePos = pos + insertedSymbol.Length;
|
||||
}
|
||||
if (Plugin.Config.SymbolPickerEnabled)
|
||||
ImGui.SameLine();
|
||||
|
||||
var beforeIcon = ImGui.GetCursorPos();
|
||||
|
||||
var tintSelector = Plugin.Config.ColorSelectedInputChannelButton && inputColour.HasValue;
|
||||
@@ -938,7 +1036,11 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
// v1.2.0 — Bottom-Status-Bar. Letzter Render-Step in DrawChatLog,
|
||||
// damit alle Zeilen-Operationen davor keine Layout-Sprünge auslösen.
|
||||
Plugin.StatusBar.Draw(Plugin);
|
||||
// v1.4.9 R2: skip on the first frame; ~12ms of first-frame layout
|
||||
// cost. User sees the StatusBar 1 frame (~17ms at 60fps) later
|
||||
// which is hidden inside the post-reload Atlas-Build window.
|
||||
if (_firstFrameDone)
|
||||
Plugin.StatusBar.Draw(Plugin);
|
||||
}
|
||||
|
||||
internal Dictionary<string, InputChannel> GetValidChannels()
|
||||
@@ -989,6 +1091,16 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
private void DrawChannelName(Tab activeTab)
|
||||
{
|
||||
// v1.4.9 R2: plain-text fallback on the first frame. ReadChannelName
|
||||
// builds SeString chunks and DrawChunks runs SeString-Renderer layout
|
||||
// — together ~18ms first-frame. Frame 1 renders the real chunks; the
|
||||
// user sees the tab name for ~17ms during the post-reload window.
|
||||
if (!_firstFrameDone)
|
||||
{
|
||||
ImGui.TextUnformatted(activeTab.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
var currentChannel = ReadChannelName(activeTab);
|
||||
if (!currentChannel.SequenceEqual(PreviousChannel))
|
||||
PreviousChannel = currentChannel;
|
||||
@@ -1622,7 +1734,7 @@ public sealed class ChatLogWindow : Window
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Warning(ex, "Error drawing chat log");
|
||||
_logger.LogWarning(ex, "Error drawing chat log");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2170,7 +2282,7 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
Plugin.Config.SeenPopOutHeaderHint = true;
|
||||
Plugin.SaveConfig();
|
||||
Plugin.LogProxy.Debug("v0.6.1 pop-out header hint dismissed");
|
||||
_logger.LogDebug("v0.6.1 pop-out header hint dismissed");
|
||||
if (openSettings)
|
||||
Plugin.SettingsWindow.Toggle();
|
||||
}
|
||||
@@ -2308,7 +2420,7 @@ public sealed class ChatLogWindow : Window
|
||||
if (PopOutWindows.Contains(tab.Identifier))
|
||||
continue;
|
||||
|
||||
var window = new Popout(this, tab, i);
|
||||
var window = new Popout(this, tab, i, _loggerFactory.CreateLogger<Popout>());
|
||||
|
||||
Plugin.WindowSystem.AddWindow(window);
|
||||
PopOutWindows.Add(tab.Identifier);
|
||||
@@ -2825,7 +2937,7 @@ public sealed class ChatLogWindow : Window
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
var safePos = viewport.WorkPos + SafeDefaultOffset;
|
||||
Position = safePos;
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"[Window-Recovery] {source}: snapping main window from {LastWindowPos} (size {LastWindowSize}) to {safePos}."
|
||||
);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Lumina.Data.Files;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoreLinq;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
@@ -67,10 +68,13 @@ public class DbViewer : Window
|
||||
|
||||
private bool NeedsScrollReset;
|
||||
|
||||
public DbViewer(Plugin plugin)
|
||||
private readonly ILogger<DbViewer> _logger;
|
||||
|
||||
public DbViewer(Plugin plugin, ILogger<DbViewer> logger)
|
||||
: base("DBViewer###chat2-dbviewer")
|
||||
{
|
||||
Plugin = plugin;
|
||||
_logger = logger;
|
||||
SelectedChannels = TabsUtil.MostlyPlayer;
|
||||
|
||||
DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
@@ -93,29 +97,13 @@ public class DbViewer : Window
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
|
||||
Plugin
|
||||
.Commands.Register(
|
||||
"/hellionView",
|
||||
"Get access to your message history, with simple filter options.",
|
||||
true
|
||||
)
|
||||
.Execute += Toggle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin
|
||||
.Commands.Register(
|
||||
"/hellionView",
|
||||
"Get access to your message history, with simple filter options.",
|
||||
true
|
||||
)
|
||||
.Execute -= Toggle;
|
||||
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||
}
|
||||
|
||||
private void Toggle(string _, string __) => Toggle();
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var totalPages = (int)Math.Ceiling((double)Count / RowPerPage);
|
||||
@@ -336,7 +324,7 @@ public class DbViewer : Window
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Failed reading messages from database");
|
||||
_logger.LogError(ex, "Failed reading messages from database");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -499,7 +487,7 @@ public class DbViewer : Window
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "FTS filter worker failed");
|
||||
_logger.LogError(ex, "FTS filter worker failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -641,7 +629,7 @@ public class DbViewer : Window
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.LogProxy.Error(ex, "Failed creating txt backup");
|
||||
_logger.LogError(ex, "Failed creating txt backup");
|
||||
|
||||
Notification.Content = "Error ...";
|
||||
Notification.Type = NotificationType.Error;
|
||||
|
||||
@@ -28,17 +28,13 @@ public class DebuggerWindow : Window, IDisposable
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
|
||||
Plugin.Commands.Register("/hellionDebugger", showInHelp: false).Execute += Toggle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.Commands.Register("/hellionDebugger", showInHelp: false).Execute -= Toggle;
|
||||
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||
}
|
||||
|
||||
private void Toggle(string _, string __) => Toggle();
|
||||
|
||||
public override unsafe void Draw()
|
||||
{
|
||||
var agent = (nint)AgentItemDetail.Instance();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using HellionChat.Branding;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Privacy;
|
||||
using HellionChat.Resources;
|
||||
@@ -38,6 +39,11 @@ public sealed class FirstRunWizard : Window
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
DrawHellionForgeAnchor();
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextWrapped(HellionStrings.Wizard_Intro);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
@@ -147,6 +153,19 @@ public sealed class FirstRunWizard : Window
|
||||
}
|
||||
}
|
||||
|
||||
// Collapsible because the full silhouette is taller than the wizard
|
||||
// window — folded by default so the privacy cards stay the primary
|
||||
// focus, expandable for whoever wants the "about the makers" anchor.
|
||||
private void DrawHellionForgeAnchor()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Hellion Forge");
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (Plugin.Interface.UiBuilder.MonoFontHandle.Push())
|
||||
ImGui.TextUnformatted(HellionForgeAscii.FoxBanner);
|
||||
}
|
||||
|
||||
private void ApplyPrivacyFirst()
|
||||
{
|
||||
Plugin.Config.PrivacyFilterEnabled = true;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
@@ -11,6 +12,7 @@ internal class Popout : Window
|
||||
private readonly ChatLogWindow ChatLogWindow;
|
||||
private readonly Tab Tab;
|
||||
private readonly int Idx;
|
||||
private readonly ILogger<Popout> _logger;
|
||||
|
||||
private long FrameTime;
|
||||
private long LastActivityTime = Environment.TickCount64;
|
||||
@@ -23,12 +25,13 @@ internal class Popout : Window
|
||||
// Exposed so AutoTellTabsService can locate this window during LRU eviction.
|
||||
internal Guid TabIdentifier => Tab.Identifier;
|
||||
|
||||
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx)
|
||||
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx, ILogger<Popout> logger)
|
||||
: base($"{tab.Name}##popout")
|
||||
{
|
||||
ChatLogWindow = chatLogWindow;
|
||||
Tab = tab;
|
||||
Idx = idx;
|
||||
_logger = logger;
|
||||
|
||||
Size = new Vector2(350, 350);
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
@@ -175,7 +178,7 @@ internal class Popout : Window
|
||||
{
|
||||
Plugin.Config.SeenPopOutInputHint = true;
|
||||
ChatLogWindow.Plugin.SaveConfig();
|
||||
Plugin.LogProxy.Debug("Pop-Out input hint dismissed");
|
||||
_logger.LogDebug("Pop-Out input hint dismissed");
|
||||
if (openSettings)
|
||||
ChatLogWindow.Plugin.SettingsWindow.Toggle();
|
||||
}
|
||||
@@ -214,13 +217,13 @@ internal class Popout : Window
|
||||
if (Tab.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.Battle;
|
||||
Plugin.LogProxy.Verbose($"Popout HideState [{Tab.Name}]: None -> Battle");
|
||||
_logger.LogTrace($"Popout HideState [{Tab.Name}]: None -> Battle");
|
||||
}
|
||||
|
||||
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.LogProxy.Verbose($"Popout HideState [{Tab.Name}]: Battle -> None");
|
||||
_logger.LogTrace($"Popout HideState [{Tab.Name}]: Battle -> None");
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -232,7 +235,7 @@ internal class Popout : Window
|
||||
if (ChatLogWindow.Plugin.Functions.Chat.CheckHideFlags())
|
||||
{
|
||||
CurrentHideState = HideState.Cutscene;
|
||||
Plugin.LogProxy.Verbose($"Popout HideState [{Tab.Name}]: None -> Cutscene");
|
||||
_logger.LogTrace($"Popout HideState [{Tab.Name}]: None -> Cutscene");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +245,7 @@ internal class Popout : Window
|
||||
&& !Plugin.GposeActive
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Verbose(
|
||||
_logger.LogTrace(
|
||||
$"Popout HideState [{Tab.Name}]: {CurrentHideState} -> None (cutscene/gpose ended)"
|
||||
);
|
||||
CurrentHideState = HideState.None;
|
||||
@@ -251,7 +254,7 @@ internal class Popout : Window
|
||||
if (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate)
|
||||
{
|
||||
CurrentHideState = HideState.CutsceneOverride;
|
||||
Plugin.LogProxy.Verbose(
|
||||
_logger.LogTrace(
|
||||
$"Popout HideState [{Tab.Name}]: Cutscene -> CutsceneOverride (user activate)"
|
||||
);
|
||||
}
|
||||
@@ -259,7 +262,7 @@ internal class Popout : Window
|
||||
if (CurrentHideState == HideState.User && ChatLogWindow.Activate)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.LogProxy.Verbose($"Popout HideState [{Tab.Name}]: User -> None (activate)");
|
||||
_logger.LogTrace($"Popout HideState [{Tab.Name}]: User -> None (activate)");
|
||||
}
|
||||
|
||||
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle
|
||||
|
||||
@@ -29,21 +29,13 @@ public class SeStringDebugger : Window
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
|
||||
#if DEBUG
|
||||
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute += Toggle;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if DEBUG
|
||||
Plugin.Commands.Register("/hellionSeString", showInHelp: false).Execute -= Toggle;
|
||||
#endif
|
||||
// Slash-command tear-down moved to Plugin.TearDownCommands.
|
||||
}
|
||||
|
||||
private void Toggle(string _, string __) => Toggle();
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (Plugin.MessageManager.LastMessage.Sender == null)
|
||||
|
||||
@@ -6,6 +6,7 @@ using Dalamud.Utility;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Ui.SettingsTabs;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
@@ -25,7 +26,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
private SettingsView View = SettingsView.Overview;
|
||||
private readonly SettingsOverview Overview;
|
||||
|
||||
internal SettingsWindow(Plugin plugin)
|
||||
internal SettingsWindow(Plugin plugin, ILoggerFactory loggerFactory)
|
||||
: base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
|
||||
{
|
||||
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
|
||||
@@ -45,13 +46,13 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
Tabs =
|
||||
[
|
||||
new General(Plugin, Mutable),
|
||||
new ThemeAndLayout(Plugin, Mutable),
|
||||
new FontsAndColours(Plugin, Mutable),
|
||||
new ThemeAndLayout(Plugin, Mutable, loggerFactory.CreateLogger<ThemeAndLayout>()),
|
||||
new FontsAndColours(Plugin, Mutable, loggerFactory.CreateLogger<FontsAndColours>()),
|
||||
new SettingsTabs.Window(Plugin, Mutable),
|
||||
new Chat(Plugin, Mutable),
|
||||
new SettingsTabs.Tabs(Plugin, Mutable),
|
||||
new SettingsTabs.Privacy(Plugin, Mutable),
|
||||
new DataManagement(Plugin, Mutable),
|
||||
new DataManagement(Plugin, Mutable, loggerFactory.CreateLogger<DataManagement>()),
|
||||
new SettingsTabs.Integrations(Plugin, Mutable),
|
||||
new Information(Mutable),
|
||||
];
|
||||
@@ -60,23 +61,11 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
DisableWindowSounds = true;
|
||||
|
||||
Initialise();
|
||||
|
||||
Plugin
|
||||
.Commands.Register("/hellion", "Perform various actions with Hellion Chat.")
|
||||
.Execute += Command;
|
||||
Plugin.Interface.UiBuilder.OpenConfigUi += Toggle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.Interface.UiBuilder.OpenConfigUi -= Toggle;
|
||||
Plugin.Commands.Register("/hellion").Execute -= Command;
|
||||
}
|
||||
|
||||
private void Command(string command, string args)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(args))
|
||||
Toggle();
|
||||
// Slash-command + OpenConfigUi tear-down moved to Plugin.TearDownCommands.
|
||||
}
|
||||
|
||||
private void Initialise()
|
||||
@@ -240,7 +229,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
}
|
||||
|
||||
if (fontChanged || fontSizeChanged || italicStateChanged)
|
||||
Plugin.FontManager.BuildFonts();
|
||||
Plugin.FontManager.RebuildDelegateFonts();
|
||||
|
||||
if (languageChanged)
|
||||
Plugin.LanguageChanged(Plugin.Interface.UiLanguage);
|
||||
|
||||
@@ -139,6 +139,12 @@ internal sealed class Chat : ISettingsTab
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Chat_SymbolPicker_Enable_Name,
|
||||
ref Mutable.SymbolPickerEnabled
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_SymbolPicker_Enable_Description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using HellionChat.Export;
|
||||
using HellionChat.Privacy;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
@@ -18,6 +19,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<DataManagement> _logger;
|
||||
|
||||
public string Name =>
|
||||
HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement";
|
||||
@@ -136,10 +138,11 @@ internal sealed class DataManagement : ISettingsTab
|
||||
),
|
||||
];
|
||||
|
||||
internal DataManagement(Plugin plugin, Configuration mutable)
|
||||
internal DataManagement(Plugin plugin, Configuration mutable, ILogger<DataManagement> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
@@ -229,7 +232,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.LogProxy.Error(e, "Unable to delete old database");
|
||||
_logger.LogError(e, "Unable to delete old database");
|
||||
WrapperUtil.AddNotification(
|
||||
Language.Options_Database_Old_Delete_Error,
|
||||
NotificationType.Error
|
||||
@@ -391,9 +394,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
Plugin.Config.RetentionLastRunAt = DateTimeOffset.UtcNow;
|
||||
Plugin.SaveConfig();
|
||||
|
||||
Plugin.LogProxy.Information(
|
||||
$"Manual retention run deleted {deleted} expired messages."
|
||||
);
|
||||
_logger.LogInformation($"Manual retention run deleted {deleted} expired messages.");
|
||||
|
||||
if (deleted > 0)
|
||||
{
|
||||
@@ -407,7 +408,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
.Wait(TimeSpan.FromSeconds(5))
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
_logger.LogWarning(
|
||||
"Retention sweep: framework refresh timed out after 5s."
|
||||
);
|
||||
}
|
||||
@@ -420,7 +421,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.LogProxy.Error(e, "Manual retention run failed");
|
||||
_logger.LogError(e, "Manual retention run failed");
|
||||
WrapperUtil.AddNotification(HellionStrings.Retention_Error, NotificationType.Error);
|
||||
}
|
||||
finally
|
||||
@@ -568,7 +569,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.LogProxy.Error(e, "Failed to compute cleanup preview");
|
||||
_logger.LogError(e, "Failed to compute cleanup preview");
|
||||
WrapperUtil.AddNotification(
|
||||
HellionStrings.Cleanup_PreviewError,
|
||||
NotificationType.Error
|
||||
@@ -589,7 +590,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
try
|
||||
{
|
||||
var deleted = Plugin.MessageManager.Store.CleanupRetainOnly(allowed);
|
||||
Plugin.LogProxy.Information($"Privacy cleanup: deleted {deleted} messages");
|
||||
_logger.LogInformation($"Privacy cleanup: deleted {deleted} messages");
|
||||
|
||||
if (
|
||||
!Plugin
|
||||
@@ -601,9 +602,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
.Wait(TimeSpan.FromSeconds(5))
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
"Privacy cleanup: framework refresh timed out after 5s."
|
||||
);
|
||||
_logger.LogWarning("Privacy cleanup: framework refresh timed out after 5s.");
|
||||
}
|
||||
|
||||
WrapperUtil.AddNotification(
|
||||
@@ -613,7 +612,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.LogProxy.Error(e, "Privacy cleanup failed");
|
||||
_logger.LogError(e, "Privacy cleanup failed");
|
||||
WrapperUtil.AddNotification(HellionStrings.Cleanup_Error, NotificationType.Error);
|
||||
}
|
||||
finally
|
||||
@@ -773,7 +772,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.LogProxy.Error(e, "Export failed");
|
||||
_logger.LogError(e, "Export failed");
|
||||
WrapperUtil.AddNotification(HellionStrings.Export_Error, NotificationType.Error);
|
||||
}
|
||||
finally
|
||||
@@ -853,7 +852,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
)
|
||||
)
|
||||
{
|
||||
Plugin.LogProxy.Warning("Clearing messages from database");
|
||||
_logger.LogWarning("Clearing messages from database");
|
||||
Plugin.MessageManager.Store.ClearMessages();
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
|
||||
@@ -911,7 +910,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
private void InsertMessages(int count)
|
||||
{
|
||||
Plugin.LogProxy.Info($"Inserting {count} messages due to user request");
|
||||
_logger.LogInformation($"Inserting {count} messages due to user request");
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var playerName = Plugin.PlayerState.CharacterName;
|
||||
@@ -956,7 +955,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
var elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
|
||||
);
|
||||
|
||||
@@ -966,7 +965,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
|
||||
);
|
||||
|
||||
@@ -977,7 +976,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
|
||||
);
|
||||
})
|
||||
@@ -990,7 +989,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
Plugin.MessageManager.FilterAllTabs();
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.LogProxy.Info(
|
||||
_logger.LogInformation(
|
||||
$"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)"
|
||||
);
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
@@ -14,14 +15,16 @@ internal sealed class FontsAndColours : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<FontsAndColours> _logger;
|
||||
|
||||
public string Name =>
|
||||
HellionStrings.Settings_Card_FontsAndColours_Title + "###tabs-fontsandcolours";
|
||||
|
||||
internal FontsAndColours(Plugin plugin, Configuration mutable)
|
||||
internal FontsAndColours(Plugin plugin, Configuration mutable, ILogger<FontsAndColours> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
@@ -312,6 +315,6 @@ internal sealed class FontsAndColours : ISettingsTab
|
||||
}
|
||||
Plugin.SaveConfig();
|
||||
GlobalParametersCache.Refresh();
|
||||
Plugin.LogProxy.Debug($"Applied chat colour preset: {preset.DisplayName}");
|
||||
_logger.LogDebug($"Applied chat colour preset: {preset.DisplayName}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Branding;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
|
||||
@@ -58,6 +59,8 @@ internal sealed class Information : ISettingsTab
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
DrawHellionForgeSection();
|
||||
ImGui.Spacing();
|
||||
DrawVersionInfoSection();
|
||||
ImGui.Spacing();
|
||||
DrawAboutSection();
|
||||
@@ -65,6 +68,20 @@ internal sealed class Information : ISettingsTab
|
||||
DrawChangelogSection();
|
||||
}
|
||||
|
||||
// Provenance anchor — folded by default so the tab opens to the
|
||||
// version-info section as before. Expands to show the full Hellion
|
||||
// Forge silhouette in monospace.
|
||||
private void DrawHellionForgeSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode("Hellion Forge");
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
using (Plugin.Interface.UiBuilder.MonoFontHandle.Push())
|
||||
ImGui.TextUnformatted(HellionForgeAscii.FoxBanner);
|
||||
}
|
||||
|
||||
private void DrawVersionInfoSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_VersionInfo_Heading);
|
||||
|
||||
@@ -4,6 +4,7 @@ using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
@@ -11,16 +12,18 @@ internal sealed class ThemeAndLayout : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<ThemeAndLayout> _logger;
|
||||
|
||||
private string? _applyDismissedFor;
|
||||
|
||||
public string Name =>
|
||||
HellionStrings.Settings_Card_ThemeAndLayout_Title + "###tabs-themeandlayout";
|
||||
|
||||
internal ThemeAndLayout(Plugin plugin, Configuration mutable)
|
||||
internal ThemeAndLayout(Plugin plugin, Configuration mutable, ILogger<ThemeAndLayout> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
@@ -90,7 +93,7 @@ internal sealed class ThemeAndLayout : ISettingsTab
|
||||
var path = Path.Combine(dir, fileName);
|
||||
var json = ThemeJsonWriter.Serialize(active);
|
||||
File.WriteAllText(path, json);
|
||||
Plugin.LogProxy.Information($"Exported active theme '{active.Slug}' to {path}");
|
||||
_logger.LogInformation($"Exported active theme '{active.Slug}' to {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Popup picker for chat-input symbol insertion. Two tabs:
|
||||
// PUA — Dalamud's SeIconChar enum (161 server-safe FFXIV glyphs)
|
||||
// BMP — server-verified Unicode symbols (whitelist built 2026-05-16)
|
||||
//
|
||||
// Render-only — the Settings-Guard for showing the trigger button lives on
|
||||
// the caller side (ChatLogWindow). Recent-Used is session state by design.
|
||||
internal sealed class SymbolPicker
|
||||
{
|
||||
private const string PopupId = "HellionSymbolPicker";
|
||||
private const int RecentCapacity = 16;
|
||||
|
||||
private string _search = string.Empty;
|
||||
private readonly List<uint> _recentUsed = new(capacity: RecentCapacity);
|
||||
|
||||
// FFXIV server-safe BMP symbols, verified 2026-05-16 via /echo + /say.
|
||||
// Filtered ranges: U+2694-26C4 (Misc Symbols Extended), U+2700+ (Dingbats
|
||||
// Extended), diagonal arrows, U+2153+ fractions, chess pieces.
|
||||
// Full probe log: Cycles/v1.4.10 BMP-Whitelist Notes.md.
|
||||
private static readonly (uint Codepoint, string Name)[] BmpWhitelist = new[]
|
||||
{
|
||||
(0x00A1u, "Inverted Exclamation"),
|
||||
(0x00A2u, "Cent Sign"),
|
||||
(0x00A3u, "Pound Sign"),
|
||||
(0x00A4u, "Currency Sign"),
|
||||
(0x00A5u, "Yen Sign"),
|
||||
(0x00A7u, "Section Sign"),
|
||||
(0x00A9u, "Copyright Sign"),
|
||||
(0x00ABu, "Left Angle Quote"),
|
||||
(0x00AEu, "Registered Sign"),
|
||||
(0x00B0u, "Degree Sign"),
|
||||
(0x00B1u, "Plus-Minus Sign"),
|
||||
(0x00B6u, "Pilcrow Sign"),
|
||||
(0x00BBu, "Right Angle Quote"),
|
||||
(0x00BCu, "One Quarter"),
|
||||
(0x00BDu, "One Half"),
|
||||
(0x00BEu, "Three Quarters"),
|
||||
(0x00BFu, "Inverted Question"),
|
||||
(0x00D7u, "Multiplication Sign"),
|
||||
(0x00F7u, "Division Sign"),
|
||||
(0x0393u, "Greek Capital Gamma"),
|
||||
(0x0394u, "Greek Capital Delta"),
|
||||
(0x0398u, "Greek Capital Theta"),
|
||||
(0x039Bu, "Greek Capital Lambda"),
|
||||
(0x039Eu, "Greek Capital Xi"),
|
||||
(0x03A0u, "Greek Capital Pi"),
|
||||
(0x03A3u, "Greek Capital Sigma"),
|
||||
(0x03A6u, "Greek Capital Phi"),
|
||||
(0x03A8u, "Greek Capital Psi"),
|
||||
(0x03A9u, "Greek Capital Omega"),
|
||||
(0x03B1u, "Greek Small Alpha"),
|
||||
(0x03B2u, "Greek Small Beta"),
|
||||
(0x03B3u, "Greek Small Gamma"),
|
||||
(0x03B4u, "Greek Small Delta"),
|
||||
(0x03B5u, "Greek Small Epsilon"),
|
||||
(0x03B6u, "Greek Small Zeta"),
|
||||
(0x03B7u, "Greek Small Eta"),
|
||||
(0x03B8u, "Greek Small Theta"),
|
||||
(0x03B9u, "Greek Small Iota"),
|
||||
(0x03BAu, "Greek Small Kappa"),
|
||||
(0x03BBu, "Greek Small Lambda"),
|
||||
(0x03BCu, "Greek Small Mu"),
|
||||
(0x03BDu, "Greek Small Nu"),
|
||||
(0x03BEu, "Greek Small Xi"),
|
||||
(0x03BFu, "Greek Small Omicron"),
|
||||
(0x03C0u, "Greek Small Pi"),
|
||||
(0x03C1u, "Greek Small Rho"),
|
||||
(0x03C3u, "Greek Small Sigma"),
|
||||
(0x03C4u, "Greek Small Tau"),
|
||||
(0x03C5u, "Greek Small Upsilon"),
|
||||
(0x03C6u, "Greek Small Phi"),
|
||||
(0x03C7u, "Greek Small Chi"),
|
||||
(0x03C8u, "Greek Small Psi"),
|
||||
(0x03C9u, "Greek Small Omega"),
|
||||
(0x2013u, "En Dash"),
|
||||
(0x2014u, "Em Dash"),
|
||||
(0x2020u, "Dagger"),
|
||||
(0x2021u, "Double Dagger"),
|
||||
(0x2026u, "Horizontal Ellipsis"),
|
||||
(0x203Bu, "Reference Mark"),
|
||||
(0x20ACu, "Euro Sign"),
|
||||
(0x2122u, "Trade Mark Sign"),
|
||||
(0x2190u, "Leftwards Arrow"),
|
||||
(0x2191u, "Upwards Arrow"),
|
||||
(0x2192u, "Rightwards Arrow"),
|
||||
(0x2193u, "Downwards Arrow"),
|
||||
(0x21D2u, "Rightwards Double Arrow"),
|
||||
(0x21D4u, "Left Right Double Arrow"),
|
||||
(0x2202u, "Partial Differential"),
|
||||
(0x2207u, "Nabla"),
|
||||
(0x2211u, "Summation"),
|
||||
(0x221Au, "Square Root"),
|
||||
(0x221Eu, "Infinity"),
|
||||
(0x222Bu, "Integral"),
|
||||
(0x2260u, "Not Equal To"),
|
||||
(0x25A0u, "Black Square"),
|
||||
(0x25A1u, "White Square"),
|
||||
(0x25B2u, "Black Up Triangle"),
|
||||
(0x25B3u, "White Up Triangle"),
|
||||
(0x25BCu, "Black Down Triangle"),
|
||||
(0x25C6u, "Black Diamond"),
|
||||
(0x25C7u, "White Diamond"),
|
||||
(0x25CBu, "White Circle"),
|
||||
(0x25CFu, "Black Circle"),
|
||||
(0x2600u, "Black Sun With Rays"),
|
||||
(0x2601u, "Cloud"),
|
||||
(0x2602u, "Umbrella"),
|
||||
(0x2603u, "Snowman"),
|
||||
(0x2605u, "Black Star"),
|
||||
(0x2606u, "White Star"),
|
||||
(0x2640u, "Female Sign"),
|
||||
(0x2642u, "Male Sign"),
|
||||
(0x2660u, "Black Spade Suit"),
|
||||
(0x2661u, "White Heart Suit"),
|
||||
(0x2663u, "Black Club Suit"),
|
||||
(0x2665u, "Black Heart Suit"),
|
||||
(0x266Au, "Eighth Note"),
|
||||
(0x2713u, "Check Mark"),
|
||||
};
|
||||
|
||||
public void OpenPopup() => ImGui.OpenPopup(PopupId);
|
||||
|
||||
// Returns the inserted codepoint as a string fragment if the user clicked
|
||||
// one this frame, or null otherwise. Caller splices the fragment into the
|
||||
// chat-input buffer at the current cursor position.
|
||||
public string? DrawAndConsume()
|
||||
{
|
||||
// ImRaii.Popup auto-disposes EndPopup, same idiom as other popups in
|
||||
// ChatLogWindow.
|
||||
using var popup = ImRaii.Popup(PopupId);
|
||||
if (!popup)
|
||||
return null;
|
||||
|
||||
string? inserted = null;
|
||||
|
||||
// Recent-Used-Row sits above the tabs so both PUA and BMP picks share
|
||||
// one fast-access strip. Session-only by design (see TrackRecent).
|
||||
if (_recentUsed.Count > 0)
|
||||
{
|
||||
ImGui.TextDisabled("Recent");
|
||||
ImGui.SameLine();
|
||||
foreach (var codepoint in _recentUsed)
|
||||
{
|
||||
var glyph = char.ConvertFromUtf32((int)codepoint);
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
glyph,
|
||||
false,
|
||||
ImGuiSelectableFlags.DontClosePopups,
|
||||
new Vector2(20, 20)
|
||||
)
|
||||
)
|
||||
{
|
||||
inserted = glyph;
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
ImGui.NewLine();
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
using (var tabs = ImRaii.TabBar("##symbolpicker-tabs"))
|
||||
{
|
||||
if (tabs)
|
||||
{
|
||||
inserted = DrawPuaTab() ?? inserted;
|
||||
inserted = DrawBmpTab() ?? inserted;
|
||||
}
|
||||
}
|
||||
|
||||
if (inserted is not null)
|
||||
TrackRecent(inserted);
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
private string? DrawPuaTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("FFXIV Icons");
|
||||
if (!tab)
|
||||
return null;
|
||||
|
||||
ImGui.InputTextWithHint(
|
||||
"##pua-search",
|
||||
"Search by name (e.g. HighQuality)",
|
||||
ref _search,
|
||||
64
|
||||
);
|
||||
|
||||
string? inserted = null;
|
||||
|
||||
if (ImGui.BeginChild("##pua-grid", new Vector2(0, 280), false))
|
||||
{
|
||||
var query = _search;
|
||||
foreach (var icon in Enum.GetValues<SeIconChar>())
|
||||
{
|
||||
var label = icon.ToString();
|
||||
if (
|
||||
query.Length > 0
|
||||
&& label.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ToIconString gives the single-codepoint glyph; tooltip
|
||||
// carries the enum name for discoverability.
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
icon.ToIconString(),
|
||||
false,
|
||||
ImGuiSelectableFlags.DontClosePopups,
|
||||
new Vector2(24, 24)
|
||||
)
|
||||
)
|
||||
{
|
||||
inserted = icon.ToIconString();
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(label);
|
||||
|
||||
// Manually-wrapping pattern from imgui_demo.cpp;
|
||||
// GetWindowContentRegionMax obsolete since ImGui 1.92, use
|
||||
// GetContentRegionAvail (see ChatLogWindow.cs:840).
|
||||
var style = ImGui.GetStyle();
|
||||
var lastItemX2 = ImGui.GetItemRectMax().X;
|
||||
var availableRightX =
|
||||
ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
|
||||
if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
ImGui.EndChild();
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
private string? DrawBmpTab()
|
||||
{
|
||||
using var tab = ImRaii.TabItem("Symbols");
|
||||
if (!tab)
|
||||
return null;
|
||||
|
||||
ImGui.InputTextWithHint("##bmp-search", "Search by name (e.g. Heart)", ref _search, 64);
|
||||
|
||||
string? inserted = null;
|
||||
|
||||
if (ImGui.BeginChild("##bmp-grid", new Vector2(0, 280), false))
|
||||
{
|
||||
var query = _search;
|
||||
foreach (var (codepoint, name) in BmpWhitelist)
|
||||
{
|
||||
if (query.Length > 0 && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var glyph = char.ConvertFromUtf32((int)codepoint);
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
glyph,
|
||||
false,
|
||||
ImGuiSelectableFlags.DontClosePopups,
|
||||
new Vector2(24, 24)
|
||||
)
|
||||
)
|
||||
{
|
||||
inserted = glyph;
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(name);
|
||||
|
||||
// Same manually-wrapping pattern as DrawPuaTab — modern API
|
||||
// since GetWindowContentRegionMax was deprecated in ImGui 1.92.
|
||||
var style = ImGui.GetStyle();
|
||||
var lastItemX2 = ImGui.GetItemRectMax().X;
|
||||
var availableRightX =
|
||||
ImGui.GetCursorScreenPos().X + ImGui.GetContentRegionAvail().X;
|
||||
if (lastItemX2 + style.ItemSpacing.X + 24f < availableRightX)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
ImGui.EndChild();
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
private void TrackRecent(string fragment)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fragment) || fragment.Length > 4)
|
||||
return;
|
||||
|
||||
var codepoint = (uint)char.ConvertToUtf32(fragment, 0);
|
||||
|
||||
// Move-to-front so the head stays the freshest pick.
|
||||
_recentUsed.RemoveAll(c => c == codepoint);
|
||||
_recentUsed.Insert(0, codepoint);
|
||||
|
||||
if (_recentUsed.Count > RecentCapacity)
|
||||
_recentUsed.RemoveAt(_recentUsed.Count - 1);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,12 @@ internal static class AutoTranslate
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
AllEntries();
|
||||
Plugin.LogProxy.Debug($"Warming up auto-translate took {sw.ElapsedMilliseconds}ms");
|
||||
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
||||
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
||||
// regressions; remains in place after Sub-Task 3.4 Befund.
|
||||
Plugin.LogProxy.Information(
|
||||
$"Warming up auto-translate took {sw.ElapsedMilliseconds}ms"
|
||||
);
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
|
||||
@@ -2,10 +2,10 @@ using System;
|
||||
|
||||
namespace HellionChat.Util;
|
||||
|
||||
// Indirection over Dalamud's IPluginLog so MessageStore can be constructed
|
||||
// in an isolated xUnit AppDomain without loading Dalamud.dll — same pattern
|
||||
// as IPlatformUtil from F12.1. A later DI-container cycle (v1.5.x) may
|
||||
// replace this with Microsoft.Extensions.Logging's ILogger<T>.
|
||||
// Plugin.LogProxy bridge for consumers that cannot take a logger via the
|
||||
// constructor: static helpers (EmoteCache et al.), Dalamud-reflected types
|
||||
// (Configuration), data classes with mass instantiation (Message) and
|
||||
// instance classes that only log from static methods (FontManager).
|
||||
internal interface IPluginLogProxy
|
||||
{
|
||||
void Verbose(string message);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Hellion Chat — ASCII branding assets
|
||||
|
||||
ASCII art collected during the Hellion Chat branding pass. The Hellion Chat mascot is a fox, and two
|
||||
variants ship inside the plugin DLL as embedded resources and render at runtime. Everything in this
|
||||
folder is the wider study collection that did not get picked for the live slots, kept around as
|
||||
reference material and for anyone who wants to remix or build on top of them.
|
||||
|
||||
## Live assets (rendered at runtime)
|
||||
|
||||
These two files live as embedded resources under `HellionChat/Resources/Branding/` and are loaded by
|
||||
the plugin at startup. They are intentionally outside this folder so the build pipeline can treat
|
||||
them as binary blobs rather than loose assets.
|
||||
|
||||
| File | Slot |
|
||||
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `HellionChat/Resources/Branding/fox-banner.txt` | Full fox silhouette with "Hellion Forge" set inside the body. Rendered as a folded `TreeNode` in the First-Run Wizard and in the Settings → Information tab. |
|
||||
| `HellionChat/Resources/Branding/fox-mini.txt` | Compact fox-head + curly-tail. Prepended to the DI-logger bootstrap banner so an `/xllog` reader sees the signature on every plugin load. |
|
||||
|
||||
## Study assets (this folder, not embedded)
|
||||
|
||||
Variants drawn during the design pass that did not get picked for the live slots. Kept around so the
|
||||
next branding iteration can remix them or pull one in if a new render slot needs an alternative
|
||||
shape.
|
||||
|
||||
| File | Description |
|
||||
| ------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `fox-profile-hf.txt` | Fox profile silhouette in `$`/`X` stipple with a small "HF" mark inside the chest. |
|
||||
| `fox-sitting-stipple.txt` | Sitting fox in `&` stipple with a full tail sweep to the right. |
|
||||
| `fox-paw-stipple.txt` | Detailed paw print in `@` stipple — five upper pads plus the main pad. |
|
||||
| `fox-paw-outline.txt` | Compact outline paw with `^` claw marks (non-retractable claws are a fox-vs-cat tell). |
|
||||
| `wolf-head-blazejkozlowski.txt` | Third-party work, kept as a style reference from the design pass — see attribution below. |
|
||||
|
||||
## License / attribution
|
||||
|
||||
### Julia Moon (all `fox-*.txt` files)
|
||||
|
||||
"Julia Moon" is the pseudonym used by the Hellion Chat maintainer for creative work. All `fox-*.txt`
|
||||
files in this folder and under `HellionChat/Resources/Branding/` were drawn for Hellion Chat under
|
||||
that name.
|
||||
|
||||
- Free to use, no attribution required, no copyright notice needed.
|
||||
- Remix, adapt, or build on top of them as you wish.
|
||||
- They travel under Hellion Chat's main EUPL-1.2 license but the author waives the attribution
|
||||
clause for these specific assets.
|
||||
|
||||
### Blazej Kozlowski (`wolf-head-blazejkozlowski.txt`)
|
||||
|
||||
The wolf head is by **Blazej Kozlowski**, originally published on the ASCII Art Archive:
|
||||
|
||||
<https://www.asciiart.eu/art/2717ef726b5e8ea6>
|
||||
|
||||
It is kept here as a style reference for the fox silhouette work. The `bug` string at the bottom of
|
||||
the art is the artist's traditional signature, not a status marker. If you want to use it yourself,
|
||||
follow the rules on asciiart.eu rather than the Julia Moon waiver above.
|
||||
@@ -0,0 +1,7 @@
|
||||
^ ^
|
||||
/ \ / \
|
||||
( ) ( )
|
||||
^ \_/ \_/ ^
|
||||
/ \ .---. / \
|
||||
\_/ / \ \_/
|
||||
\_____/
|
||||
@@ -0,0 +1,28 @@
|
||||
@@
|
||||
@@ @@@@ @@
|
||||
@@@@@ @@@@@@ @@@@@
|
||||
@@@@@@ @@@@@@ @@@@@@
|
||||
@@@@@@ @@@@@@ @@@@@@
|
||||
@@ @@@@@ @@@ @@@@@ @@@
|
||||
@@@@@ @@@@@@
|
||||
@@@@@@@ @@@@@@ @@@@@@ @@@@@@
|
||||
@@@@@@ @@@@@@@@@@ @@@@@@@@@@ @@@@@@@
|
||||
@@@@@ @@@@@@@@@@@ @@@@@@@@@@@ @@@@@
|
||||
@@@@@ @@@@@@@@@@@ @@@@@@@@@@@ @@@@@@
|
||||
@@@@@@@ @@@@@@@@@@@ @@@@@@@@@@@ @@@@@@@
|
||||
@@@@@@@ @@@@@@@@@@ @@@@@@@@@@ @@@@@@@
|
||||
@@@@@ @@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@ @@@@@
|
||||
@@@@@@@@@@ @@@@@ @@@@@ @@@@@@@@@@
|
||||
@@@@@@@@@@@@ @@@@@@@@@@@@
|
||||
@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@
|
||||
@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@@
|
||||
@@@@@@@@@@@ @@@@@@@@@@@@@@ @@@@@@@@@@@
|
||||
@@@@@@@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@
|
||||
@@@@@ @@@@@@@@@@@@@@@@@@ @@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@@@@@@@@@@@@@@@@@@@@@
|
||||
@@@ @@@
|
||||
@@ -0,0 +1,37 @@
|
||||
:;. ::;+xXX$$$$$$Xx+:.
|
||||
X+++++;;;;;+xXX$$$$$$$$$$$$$$$$$$$X;.
|
||||
$; ..:::X$$$$$$$$$$$$$$$$$$$$$$$$$$+.
|
||||
XX. :X$$$$$$$$$$$$$$$$$$$Xx+;;;+X$$:
|
||||
:$X: :+xxX$$$$$$$$$$$$$$$X;: +$$$$$$$X;.
|
||||
;$$x:..;$$$$$$$$$$$$X;. HF +$$$$$$$$$$$$;
|
||||
.;$$$$$$$$$$$$$$+: :X$$$$$$$$$$$$$X:
|
||||
:+XXXX+;. ;X$$$$$$$$$$$$$$X:
|
||||
:X$$$$$$$$$$$$$$$+.
|
||||
:X$$$x$$$$$$$$$$$+.
|
||||
.;X$$$+$$$$$$$$$$$X:
|
||||
.+$$$$$+$$$$$$$$$$$$;
|
||||
:X$$$$$+$$$$$$$$$$$$:
|
||||
:X$X;::$$$$$$$$$$$$;
|
||||
;$$; :$$$$$$$$$$$$X
|
||||
+$$ :$$$$$$$$$$$$$
|
||||
;$X.+$$$$$$$$$$$$$
|
||||
;$$$$$$$$$$$$$X
|
||||
x$$$$$$$$$$$$$$+
|
||||
:X$$$$$$$$$$$$$$$$$$
|
||||
:X$$$$$$$$$$$$$$$$$$x+
|
||||
+x: X$$$$$$XX$$$$$$$$$$$+XX
|
||||
:;X$;xxX$Xx+XXX$$$$$$$$$;.+:
|
||||
;+X$$$$$$$$X.XX$$$$$$$$+:x;
|
||||
.+$$$$$$$$$$+++$$$$$$$$x.;+:
|
||||
;$$$$$$$$$$$$$$x$$$$$$$X+;
|
||||
.+;;X$$$x;;+X$$$+;$$$$$$+.
|
||||
:;:X$+.++. ;$+.;+X$$$$X.
|
||||
.+$$: X;:::X$$$$:
|
||||
:X+. :xX$$+.. .x$$$$:
|
||||
:$+.;+: :$$+. .+$$$X.
|
||||
:$$; .x$$$;
|
||||
;X; .$$$X:
|
||||
+$$$+.
|
||||
:X$$+.
|
||||
;X$+
|
||||
.+$+.
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
+$: ;+:
|
||||
;&$:+. :$x:X
|
||||
.$&x .$;.X$: +X
|
||||
$&X :.x&X$ ..$X
|
||||
:$&&: ;$&X$&&&&X
|
||||
X&&X+$&&&&&&&&&x:
|
||||
X&&&&&&&&&&&&&&&&+
|
||||
;$&&&&&&&&&&&&&&&&&&;
|
||||
;&&&&&&&&&&&&&&&&&&&&&&&+
|
||||
.X+;&&&&&&&&&&&&&$+:x&&&&$;
|
||||
.:;&&&&&&&&&&&&&&&&&&&&&&&+
|
||||
.$&&&&&&&&&&&&&&&&&&&&&&&&&:
|
||||
:$;X&&&&&&&&&&&&&$XX$&&&&&&&$:
|
||||
:.$&&&&&&&&&&$; ;X&&&&&+.
|
||||
:&&&&&&&&&&&; :+;. :$&;
|
||||
;+&&&&&&&&$. :++: ::
|
||||
.&&&&&&&&&:.. ::
|
||||
:&X&&&&&&$;+ X:
|
||||
.:$&&&&&&&X ;&:
|
||||
X&&&&&&&&: X&+
|
||||
;&&&&&&&&& :: :&&x
|
||||
;&&&&&&&&&+X .$&&+
|
||||
:x$&&&&&&&&&&&&&&&. :: X&&$
|
||||
.+$&&&&&&&&X+:&&&&&&&&&.;X X.$&&X
|
||||
;$&&&&&&&&&$+&&:$&&&&&&&&+$+ X+&&&;
|
||||
.x&&&&&&&&&&&$;&&&$:&&&&&&&&&&X .; X&&&X
|
||||
+&&&&&&&&&&&&$+&&&&&&:+&&&&&&&&&: :X:&&&X.
|
||||
.$&&&&&&&&&&&&&+$&&&&&&&$;X&&&&&&&$: ;&&&&+
|
||||
:&&&&&&&&&&&&&&$;&&&&&&&&&&$:$&&&&&&&$::+ ;&&&&; ::::.
|
||||
$&&&&&&&&&&&&&&X:&&&&&&&&&&&&;$&&&&&&Xx&&&&&&&+$&&&&&&&:
|
||||
X&&&&&&&&&&&&&&&X.X&&&&&&&&&&&X X&&&&&&::+&&&&X&&&&&&&&&X
|
||||
:&&&&&&&&&&&&&&&&$:X$&&&&&&&&&&&::&&&$XX++&&&&&xx+X&&&&&&+
|
||||
;&&&&&&&&&&&&&&&&&$.:$&&&&&&&&$X.:;X&&&&&&&&&&&&&&X+&&&&$
|
||||
+&&&&&&&&&&&&&&&&&&&X:+x+++X$&&&&&&&&&&&&&&&&&&&&&;x&&&&;
|
||||
x&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&$;$$$XXXX
|
||||
+&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&x:X$XXX:
|
||||
:&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&$$&&&. ;$+.
|
||||
+&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&X: .+$;
|
||||
+&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&$x+;;;:. .$$.
|
||||
:$&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&+. .$&.
|
||||
+&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&X: .+&&&x x&;
|
||||
:X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&X;. :&&$X&+
|
||||
.;X$&&&&&&&&&&&&&&&&&&&$X+:. :&&&&:
|
||||
.;xXX$$$$XX+;. .$&&x
|
||||
:&&+
|
||||
:&x.
|
||||
:+;
|
||||
@@ -0,0 +1,26 @@
|
||||
__
|
||||
.d$$b
|
||||
.' TO$;\
|
||||
/ : TP._;
|
||||
/ _.; :Tb|
|
||||
/ / ;j$j
|
||||
_.-" d$$$$
|
||||
.' .. d$$$$;
|
||||
/ /P' d$$$$P. |\
|
||||
/ " .d$$$P' |\^"l
|
||||
.' `T$P^""""" :
|
||||
._.' _.' ;
|
||||
`-.-".-'-' ._. _.-" .-"
|
||||
`.-" _____ ._ .-"
|
||||
-(.g$$$$$$$b. .'
|
||||
""^^T$$$P^) .(:
|
||||
_/ -" /.' /:/;
|
||||
._.'-'`-' ")/ /;/;
|
||||
`-.-"..--"" " / / ;
|
||||
.-" ..--"" -' :
|
||||
..--""--.-" (\ .-(\
|
||||
..--"" `-\(\/;`
|
||||
_. :
|
||||
;`-
|
||||
:\
|
||||
; bug
|
||||
+394
-106
@@ -1,110 +1,398 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[15.0.0, )",
|
||||
"resolved": "15.0.0",
|
||||
"contentHash": "411vwC8/X8Z/sQ2TI6v3SvOn66xFPeOjFn3Zn+h0d3Ox2t1kFm66AhDvmx/qcMwVrR+Hidxj0dadpQ2dgyXMBQ=="
|
||||
},
|
||||
"DotNet.ReproducibleBuilds": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.39, )",
|
||||
"resolved": "1.2.39",
|
||||
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.4, 4.0.0)",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "3.1.4",
|
||||
"MessagePackAnalyzer": "3.1.4",
|
||||
"Microsoft.NET.StringTools": "17.11.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "DZ6G2QuyPrsh5VS+wfiZbNBtYT6p+CkxXjD0aZHF04xso7QsG/uk0JpG30hzYlK6u/wtTzta1Dqfgbc/Sl2sDA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "10.0.7",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.11",
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
},
|
||||
"morelinq": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.4.0, )",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "QX3bsK9oFeUXk8tFsc9NkI6NnCr8Ar/ex027p+ZZ/jdLCdX2RlryDtxUqZW5j45NVwn4E4Z4hzupsoMQd6Yxtg=="
|
||||
},
|
||||
"Pidgin": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.5.1, 4.0.0)",
|
||||
"resolved": "3.5.1",
|
||||
"contentHash": "zU7tkXlF3D6d2GLTjJDomAL3nnl4AwfZvSgSz8D4b+Ry21/clqedYlxBnEAkAU/bkGfEv6uRR7QCdWZUpKrB/g=="
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.12, 4.0.0)",
|
||||
"resolved": "3.1.12",
|
||||
"contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A=="
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.50.3, )",
|
||||
"resolved": "3.50.3",
|
||||
"contentHash": "tVyhqQ8wxgedWiiPFChyZhE8I3PkOM/AE1azsj1qsdYUws13ONBFyi3aDxju4tD2kzedB2q5+50WrTyY0h2gMQ=="
|
||||
},
|
||||
"MessagePack.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ=="
|
||||
},
|
||||
"MessagePackAnalyzer": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "xVrtBg3M1wJlBDkoT0dXEYB/wSc8bIHJPYtw/bu1AqpWgF79uPSs87DAhERR/Ilumre6TKZa1cjMg3VUUObVLA==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.StringTools": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.11.4",
|
||||
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.11",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.11"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
}
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[15.0.0, )",
|
||||
"resolved": "15.0.0",
|
||||
"contentHash": "411vwC8/X8Z/sQ2TI6v3SvOn66xFPeOjFn3Zn+h0d3Ox2t1kFm66AhDvmx/qcMwVrR+Hidxj0dadpQ2dgyXMBQ=="
|
||||
},
|
||||
"DotNet.ReproducibleBuilds": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.39, )",
|
||||
"resolved": "1.2.39",
|
||||
"contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
|
||||
},
|
||||
"MessagePack": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.4, 4.0.0)",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "BH0wlHWmVoZpbAPyyt2Awbq30C+ZsS3eHSkYdnyUAbqVJ22fAJDzn2xTieBeoT5QlcBzp61vHcv878YJGfi3mg==",
|
||||
"dependencies": {
|
||||
"MessagePack.Annotations": "3.1.4",
|
||||
"MessagePackAnalyzer": "3.1.4",
|
||||
"Microsoft.NET.StringTools": "17.11.4"
|
||||
}
|
||||
},
|
||||
"Microsoft.Data.Sqlite": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, )",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "DZ6G2QuyPrsh5VS+wfiZbNBtYT6p+CkxXjD0aZHF04xso7QsG/uk0JpG30hzYlK6u/wtTzta1Dqfgbc/Sl2sDA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "10.0.7",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.11",
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, 11.0.0)",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Hosting": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, 11.0.0)",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "M/vBpfWcschvS2EUeq7cHfscsxabiGTptXwV7GeSueovGiSoNjyo1j5PMcWuOAAQrRW3nRqxZk8NeumrmpzUBg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Binder": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.CommandLine": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Json": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": "10.0.7",
|
||||
"Microsoft.Extensions.DependencyInjection": "10.0.7",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Diagnostics": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "10.0.7",
|
||||
"Microsoft.Extensions.Hosting.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Console": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Debug": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.EventLog": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.EventSource": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, 11.0.0)",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "hOeRIQ63GkgiYCB/MIFp+LQs8aXpJXpB55t6Aj37ab7t2/6WeFcPXxYM9hdy/o5tffzwf8mhqzLJP6mjGYCxjw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options": {
|
||||
"type": "Direct",
|
||||
"requested": "[10.0.7, 11.0.0)",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"morelinq": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.4.0, )",
|
||||
"resolved": "4.4.0",
|
||||
"contentHash": "QX3bsK9oFeUXk8tFsc9NkI6NnCr8Ar/ex027p+ZZ/jdLCdX2RlryDtxUqZW5j45NVwn4E4Z4hzupsoMQd6Yxtg=="
|
||||
},
|
||||
"Pidgin": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.5.1, 4.0.0)",
|
||||
"resolved": "3.5.1",
|
||||
"contentHash": "zU7tkXlF3D6d2GLTjJDomAL3nnl4AwfZvSgSz8D4b+Ry21/clqedYlxBnEAkAU/bkGfEv6uRR7QCdWZUpKrB/g=="
|
||||
},
|
||||
"SixLabors.ImageSharp": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.1.12, 4.0.0)",
|
||||
"resolved": "3.1.12",
|
||||
"contentHash": "iAg6zifihXEFS/t7fiHhZBGAdCp3FavsF4i2ZIDp0JfeYeDVzvmlbY1CNhhIKimaIzrzSi5M/NBFcWvZT2rB/A=="
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3": {
|
||||
"type": "Direct",
|
||||
"requested": "[3.50.3, )",
|
||||
"resolved": "3.50.3",
|
||||
"contentHash": "tVyhqQ8wxgedWiiPFChyZhE8I3PkOM/AE1azsj1qsdYUws13ONBFyi3aDxju4tD2kzedB2q5+50WrTyY0h2gMQ=="
|
||||
},
|
||||
"MessagePack.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "aVWrDAkCdqxwQsz/q0ldPh2EFn48M99YUzE9OvZjMq2RNLKz4o2z88iGFvSvbMqOWRweRvKPHBJZe22PRqzslQ=="
|
||||
},
|
||||
"MessagePackAnalyzer": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.1.4",
|
||||
"contentHash": "CTaSsN/liJ7MhLCAB7Z4ZLBNuVGCq9lt2BT/cbrc9vzGv89yK3CqIA+z9T19a11eQYl9etZHL6MQJgCqECRVpg=="
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "xVrtBg3M1wJlBDkoT0dXEYB/wSc8bIHJPYtw/bu1AqpWgF79uPSs87DAhERR/Ilumre6TKZa1cjMg3VUUObVLA==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Binder": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.CommandLine": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "3lNjglxfFxOzI9zG+3HSg/YSGqo//8Fqw6u6iuIamZb4JCorbA3JLaeWOpfKTAPi2UJwaispOXWx14dUqcGz4A==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "qbZLvLsoTdArSloEnSxs21P781YUmwVmHc5NJPQD/ezAreQ7884z+6QfAZVKi86WAZtzx83jK2uC4itxOM44gQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.Json": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "64dimvyyKk0dbUbrLg/YCv4ugJ4sVz2aXLwfvZwR1EC4tJqW9ru/oVRcXwoJRa2lQGXtYtlpk4maWOeIb48tQw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Configuration.UserSecrets": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "YqVIICoIdl0016wkeO2WQS+uEbEXbUhMLKdC5rZNl1X3nu59F+nwaAHdHjq/4OK+Cx31DYmNUSFh+MUot8qSDw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Json": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Physical": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw=="
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "l+smp1qPlU0OUXD0OGfdp7OUFrbdq7ZaP5T7m2WpfZ4RFKD7iG73BAT7tjSMxNmbSXkhAn1jYHOAqzYG1r9sNg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "uJ9JP677y+uy+C0vtaSfi7XXgFAdz8DhU3M9lwwIXDfQKcyQ0yxM9DVYa0NXDtdVTYA2eBUtVFZ8LY0GCdeE/w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "teioDgVpi8L186wUfrXQV1YuBt6lCSPmFZiMZo53+FZxHFjOV+f4GXo4LXgJ273Mku9//AdXWVjk9J7eJP6inw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileProviders.Physical": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "zhgWg/i0ECj5v0jLFBSZHplvc5ygCI91DR4nne+BP4XAKF5ycz0pEKnFiTw8C1jCABJEZsnBZh6pXAvn71kFmw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileSystemGlobbing": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.FileSystemGlobbing": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "NTUspqB+vH9g4wAD6KPOBx01xqYuKXR/cHXm449zpbq1GqfjdAxBmg7eJXrNsPw7SKwIdT2cJ05GxYVvc+lvsA=="
|
||||
},
|
||||
"Microsoft.Extensions.Hosting.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "5s8d6qC6EA8UOI4wR/+zlsq7SXttJMRb9d7zvVZ7+bE3CQEfVtC9ITUDCommm87R1zzj6WJBbCnztuIJXnP3DA==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Configuration": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "7BBnoGF37USiu7j434put9mDp7EjdlNDIZsR4vHfC1FbLZeLqiWjgJbeEtF0p59Ryqt8AtraHawf0ZKbe5jibg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Binder": "10.0.7",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7",
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Console": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "DA++Es6v6W0HfrOrw+K8WyN6jNnZHp640PDdEvl8yfeVmgflKdn6vSSFvufNUSOuY+M2ZaSUgfY+jUKtNpXcCw==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Configuration": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Debug": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "Y6DSt/JZApunYWKqTtqbdsR6iqAvHx3D0tavbNJ1rnC24MUpF+3XO/VKgFi+9PFqMyvQ2GHBBGb8H3cLSw7rDg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "1C8eTuxF6BLncNSJ1HCfmaBcjpUSqQDPlBVdYTlet9oldHTPpNh9iatxSJLs8TOqdp/FOpH+nSLdBve7fu9mTQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7",
|
||||
"System.Diagnostics.EventLog": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.EventSource": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "YWfndnDX1jVMGCN8d5T+rO+BO8sDw6BkYlUk0BYui+WP7+HhlWx8QLdA4yUDjrkGVb3AQxIWWEPVKw5Nnfj5GQ==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Logging": "10.0.7",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options.ConfigurationExtensions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Configuration.Binder": "10.0.7",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
|
||||
"Microsoft.Extensions.Options": "10.0.7",
|
||||
"Microsoft.Extensions.Primitives": "10.0.7"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Primitives": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw=="
|
||||
},
|
||||
"Microsoft.NET.StringTools": {
|
||||
"type": "Transitive",
|
||||
"resolved": "17.11.4",
|
||||
"contentHash": "mudqUHhNpeqIdJoUx2YDWZO/I9uEDYVowan89R6wsomfnUJQk6HteoQTlNjZDixhT2B4IXMkMtgZtoceIjLRmA=="
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.11",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.11"
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA=="
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.1.11",
|
||||
"contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==",
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.11"
|
||||
}
|
||||
},
|
||||
"System.Diagnostics.EventLog": {
|
||||
"type": "Transitive",
|
||||
"resolved": "10.0.7",
|
||||
"contentHash": "WbmDLeTPYhEzXhvYVioTVn/D1XX6bovyny9n5p8Zxtf03+eY385RB818teZm6n+fA63iZNvng0/Np4tLuhkMhQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,82 +3,90 @@
|
||||
## Acknowledgements
|
||||
|
||||
HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) by
|
||||
**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and **[Anna](https://github.com/anna-is-cute)**, both of whom
|
||||
kept that plugin running and maintained for years before I ever opened the source. Without their work this fork would
|
||||
not exist, full stop. I owe them the architecture, the message store, the channel filtering, the sidebar tab system, the
|
||||
hooks into FFXIV's chat, the localisation infrastructure, and countless small decisions that I only noticed because they
|
||||
had already been made correctly.
|
||||
**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
|
||||
**[Anna](https://github.com/anna-is-cute)**, both of whom kept that plugin running and maintained
|
||||
for years before I ever opened the source. Without their work this fork would not exist, full stop.
|
||||
I owe them the architecture, the message store, the channel filtering, the sidebar tab system, the
|
||||
hooks into FFXIV's chat, the localisation infrastructure, and countless small decisions that I only
|
||||
noticed because they had already been made correctly.
|
||||
|
||||
If you find HellionChat useful, please remember that the foundation came from Chat 2. The code Anna and Infi wrote is
|
||||
doing most of the heavy lifting in this fork too.
|
||||
If you find HellionChat useful, please remember that the foundation came from Chat 2. The code Anna
|
||||
and Infi wrote is doing most of the heavy lifting in this fork too.
|
||||
|
||||
## A direct word to Infi and Anna
|
||||
|
||||
Hi. I am Florian. I forked Chat 2 because I wanted a privacy-by-default version for my own use case and a small group of
|
||||
friends I play with, not because I thought I could do anything better than what you built. The opposite is true.
|
||||
ChatTwo's default of full history and cross-character logging is the right call for most users. I just have a different
|
||||
threat model and a different data-handling philosophy that fits a smaller, locally-stored, retention- limited approach.
|
||||
Hi. I am Florian. I forked Chat 2 because I wanted a privacy-by-default version for my own use case
|
||||
and a small group of friends I play with, not because I thought I could do anything better than what
|
||||
you built. The opposite is true. ChatTwo's default of full history and cross-character logging is
|
||||
the right call for most users. I just have a different threat model and a different data-handling
|
||||
philosophy that fits a smaller, locally-stored, retention- limited approach.
|
||||
|
||||
What HellionChat adds is mostly Hellion-specific surface area: a privacy filter, per-channel retention windows, an
|
||||
export pipeline, an Auto-Tell- Tabs feature for FFXIV club greeters, the Hellion theme and font, German localisation,
|
||||
and a settings UX rebuild. None of it touches the bones of what you built. Where I had to modify your code I tried to
|
||||
keep the edits minimal, isolated to clearly-marked Hellion files, and reversible.
|
||||
What HellionChat adds is mostly Hellion-specific surface area: a privacy filter, per-channel
|
||||
retention windows, an export pipeline, an Auto-Tell- Tabs feature for FFXIV club greeters, the
|
||||
Hellion theme and font, German localisation, and a settings UX rebuild. None of it touches the bones
|
||||
of what you built. Where I had to modify your code I tried to keep the edits minimal, isolated to
|
||||
clearly-marked Hellion files, and reversible.
|
||||
|
||||
Concrete example: when API 15 hit, I cherry-picked your fix for the BetterTTV emote regression with `git cherry-pick -x`
|
||||
so authorship and co-author trail stay intact. That was the standard I held to as long as cherry-picking was viable, and
|
||||
you should never have to look at this fork and wonder if I quietly ate your work.
|
||||
Concrete example: when API 15 hit, I cherry-picked your fix for the BetterTTV emote regression with
|
||||
`git cherry-pick -x` so authorship and co-author trail stay intact. That was the standard I held to
|
||||
as long as cherry-picking was viable, and you should never have to look at this fork and wonder if I
|
||||
quietly ate your work.
|
||||
|
||||
With ChatTwo entering its rework cycle, the active cherry-pick pipeline is closed since v1.4.x — see
|
||||
[docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md) for the full reasoning. The attribution standard stays exactly the same:
|
||||
every existing `(cherry picked from commit ...)` line remains in the git history, the EUPL-1.2 anchor lines in source
|
||||
files are untouched, and this NOTICE.md remains canonical. If anything from this point forward originates from Chat 2 it
|
||||
will be a hand-port at most, called out as such in the commit message and source comments, not a `git cherry-pick`.
|
||||
[docs/UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md) for the full reasoning. The attribution standard
|
||||
stays exactly the same: every existing `(cherry picked from commit ...)` line remains in the git
|
||||
history, the EUPL-1.2 anchor lines in source files are untouched, and this NOTICE.md remains
|
||||
canonical. If anything from this point forward originates from Chat 2 it will be a hand-port at
|
||||
most, called out as such in the commit message and source comments, not a `git cherry-pick`.
|
||||
|
||||
If anything in this fork ever steps on something you would not be okay with, please reach out and I will fix it.
|
||||
Genuinely. The list of contacts is below.
|
||||
If anything in this fork ever steps on something you would not be okay with, please reach out and I
|
||||
will fix it. Genuinely. The list of contacts is below.
|
||||
|
||||
## Maintainer contact
|
||||
|
||||
If something in HellionChat causes problems, especially if it relates back to Chat 2 or to anything Infi or Anna would
|
||||
want flagged:
|
||||
If something in HellionChat causes problems, especially if it relates back to Chat 2 or to anything
|
||||
Infi or Anna would want flagged:
|
||||
|
||||
- **Gitea Issues:**
|
||||
[JonKazama-Hellion/HellionChat/issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
|
||||
- **Discord:** `@j.j_kazama`
|
||||
- **Email (business):** <kontakt@hellion-media.de>
|
||||
|
||||
I respond on weekdays during European business hours. For anything urgent (security, attribution, takedown), email is
|
||||
the fastest path.
|
||||
I respond on weekdays during European business hours. For anything urgent (security, attribution,
|
||||
takedown), email is the fastest path.
|
||||
|
||||
## Why this fork is not upstreamed
|
||||
|
||||
The privacy-by-default position fits a small audience. ChatTwo's full-history-by-default position fits a much larger
|
||||
one, including the roleplaying community where chat archive is part of the play experience. Trying to upstream
|
||||
HellionChat's defaults would have meant arguing that Chat 2's defaults are wrong, and they are not. They are right for
|
||||
the user base ChatTwo serves. So I keep the fork separate and attribute clearly. Active cherry-picking from upstream
|
||||
stopped in the v1.4.x cycle once Chat 2's rework made selective patches no longer portable; the existing cherry-pick
|
||||
trail stays in the git history.
|
||||
The privacy-by-default position fits a small audience. ChatTwo's full-history-by-default position
|
||||
fits a much larger one, including the roleplaying community where chat archive is part of the play
|
||||
experience. Trying to upstream HellionChat's defaults would have meant arguing that Chat 2's
|
||||
defaults are wrong, and they are not. They are right for the user base ChatTwo serves. So I keep the
|
||||
fork separate and attribute clearly. Active cherry-picking from upstream stopped in the v1.4.x cycle
|
||||
once Chat 2's rework made selective patches no longer portable; the existing cherry-pick trail stays
|
||||
in the git history.
|
||||
|
||||
## Why HellionChat left the GitHub fork network
|
||||
|
||||
The Dalamud plugin ecosystem treats the GitHub-Fork relation as a signal that a fork is either a development branch or a
|
||||
dead mirror. HellionChat is neither. It is an independently-maintained EUPL-1.2 fork with its own release cadence, its
|
||||
own custom repo, its own user base. Detaching the fork-network relation just makes the situation honest. The git
|
||||
history, the existing cherry-pick trail, and the attribution stay exactly the same. The only thing that changes is the
|
||||
GitHub UI no longer says "forked from".
|
||||
The Dalamud plugin ecosystem treats the GitHub-Fork relation as a signal that a fork is either a
|
||||
development branch or a dead mirror. HellionChat is neither. It is an independently-maintained
|
||||
EUPL-1.2 fork with its own release cadence, its own custom repo, its own user base. Detaching the
|
||||
fork-network relation just makes the situation honest. The git history, the existing cherry-pick
|
||||
trail, and the attribution stay exactly the same. The only thing that changes is the GitHub UI no
|
||||
longer says "forked from".
|
||||
|
||||
## Trademarks and naming
|
||||
|
||||
"Chat 2" and "ChatTwo" are the names Infi and Anna chose for the upstream plugin. HellionChat does not use either of
|
||||
those names in user-facing copy except where required to describe origin (settings tab, manifest, this file, the
|
||||
README). The Hellion brand is mine.
|
||||
"Chat 2" and "ChatTwo" are the names Infi and Anna chose for the upstream plugin. HellionChat does
|
||||
not use either of those names in user-facing copy except where required to describe origin (settings
|
||||
tab, manifest, this file, the README). The Hellion brand is mine.
|
||||
|
||||
## Questions
|
||||
|
||||
This file is the canonical place for "is this attribution correct, is the maintainer reachable, is the relationship to
|
||||
Chat 2 documented". If anything in here is wrong, please open an issue or contact me directly.
|
||||
This file is the canonical place for "is this attribution correct, is the maintainer reachable, is
|
||||
the relationship to Chat 2 documented". If anything in here is wrong, please open an issue or
|
||||
contact me directly.
|
||||
|
||||
---
|
||||
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
|
||||
[hellion-media.de](https://hellion-media.de)
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
|
||||
Harzburg | [hellion-media.de](https://hellion-media.de)
|
||||
|
||||
+112
-96
@@ -1,12 +1,14 @@
|
||||
# Privacy notice
|
||||
|
||||
HellionChat is a Dalamud plugin for FINAL FANTASY XIV, focused on giving the user explicit control over what their chat
|
||||
client stores locally. This document describes what the plugin does with your data, what it does not do, and how you
|
||||
exercise the rights the GDPR gives you over data you generate yourself.
|
||||
HellionChat is a Dalamud plugin for FINAL FANTASY XIV, focused on giving the user explicit control
|
||||
over what their chat client stores locally. This document describes what the plugin does with your
|
||||
data, what it does not do, and how you exercise the rights the GDPR gives you over data you generate
|
||||
yourself.
|
||||
|
||||
This document is informational. The maintainer of HellionChat is **not** a controller or processor of your data in the
|
||||
GDPR sense, because no data ever leaves your machine on the maintainer's infrastructure. Independently of that, the
|
||||
plugin is built so that you can act on your own data the way the GDPR expects.
|
||||
This document is informational. The maintainer of HellionChat is **not** a controller or processor
|
||||
of your data in the GDPR sense, because no data ever leaves your machine on the maintainer's
|
||||
infrastructure. Independently of that, the plugin is built so that you can act on your own data the
|
||||
way the GDPR expects.
|
||||
|
||||
Last reviewed: 2026-05-05 (HellionChat v1.1.0).
|
||||
|
||||
@@ -14,34 +16,37 @@ Last reviewed: 2026-05-05 (HellionChat v1.1.0).
|
||||
|
||||
## TL;DR
|
||||
|
||||
- All chat data the plugin stores stays on your machine, in your Dalamud `pluginConfigs/HellionChat/` directory.
|
||||
- The plugin does not phone home. No telemetry, no analytics, no crash reporter, no usage counter, no remote update
|
||||
check beyond what Dalamud itself does.
|
||||
- One outbound network call exists by design: the BetterTTV emote service (for chat emotes). It is documented in detail
|
||||
below and can be reasoned about per request.
|
||||
- You can export every message the plugin has stored, in Markdown, JSON or CSV, and you can wipe stored history per
|
||||
channel, per date range, or globally.
|
||||
- All chat data the plugin stores stays on your machine, in your Dalamud
|
||||
`pluginConfigs/HellionChat/` directory.
|
||||
- The plugin does not phone home. No telemetry, no analytics, no crash reporter, no usage counter,
|
||||
no remote update check beyond what Dalamud itself does.
|
||||
- One outbound network call exists by design: the BetterTTV emote service (for chat emotes). It is
|
||||
documented in detail below and can be reasoned about per request.
|
||||
- You can export every message the plugin has stored, in Markdown, JSON or CSV, and you can wipe
|
||||
stored history per channel, per date range, or globally.
|
||||
|
||||
---
|
||||
|
||||
## What the plugin stores locally
|
||||
|
||||
HellionChat keeps three kinds of state on your machine, all under `%appdata%\XIVLauncher\pluginConfigs\HellionChat\` on
|
||||
Windows (`~/.xlcore/pluginConfigs/HellionChat/` on Linux/macOS via XIVLauncher Core):
|
||||
HellionChat keeps three kinds of state on your machine, all under
|
||||
`%appdata%\XIVLauncher\pluginConfigs\HellionChat\` on Windows
|
||||
(`~/.xlcore/pluginConfigs/HellionChat/` on Linux/macOS via XIVLauncher Core):
|
||||
|
||||
1. **Configuration** (`HellionChat.json`). Plugin settings, channel whitelist, retention values, layout state, theme
|
||||
colours. Contains no chat content.
|
||||
1. **Configuration** (`HellionChat.json`). Plugin settings, channel whitelist, retention values,
|
||||
layout state, theme colours. Contains no chat content.
|
||||
|
||||
2. **Message database** (SQLite file in the same directory). Chat messages from the channels on your whitelist, stored
|
||||
as MessagePack-encoded blobs. The default whitelist out of the box covers only your own conversations: tells, party,
|
||||
free company, linkshells, cross-world linkshells, alliance, ExtraChat. Public chat, NPC dialogue, system messages and
|
||||
battle logs are dropped on the storage layer and never written to disk.
|
||||
2. **Message database** (SQLite file in the same directory). Chat messages from the channels on your
|
||||
whitelist, stored as MessagePack-encoded blobs. The default whitelist out of the box covers only
|
||||
your own conversations: tells, party, free company, linkshells, cross-world linkshells, alliance,
|
||||
ExtraChat. Public chat, NPC dialogue, system messages and battle logs are dropped on the storage
|
||||
layer and never written to disk.
|
||||
|
||||
3. **Cached emote images** (`EmoteCacheV1/` directory). Image files downloaded from BetterTTV when an emote appears in a
|
||||
message you receive. See "Outbound network calls" below.
|
||||
3. **Cached emote images** (`EmoteCacheV1/` directory). Image files downloaded from BetterTTV when
|
||||
an emote appears in a message you receive. See "Outbound network calls" below.
|
||||
|
||||
There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/HellionChat/` is independent from
|
||||
`pluginConfigs/ChatTwo/`.
|
||||
There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/HellionChat/` is
|
||||
independent from `pluginConfigs/ChatTwo/`.
|
||||
|
||||
### Retention defaults
|
||||
|
||||
@@ -49,44 +54,49 @@ There is no shared state with the upstream Chat 2 plugin. `pluginConfigs/Hellion
|
||||
- Your-conversation channels (party, FC, linkshells, cross-world LS, alliance, ExtraChat): 90 days
|
||||
- Global default for anything else: 30 days
|
||||
|
||||
**Retention is off by default.** The plugin does not delete anything on its own until you explicitly turn the retention
|
||||
sweep on in the settings. Until then, stored messages stay until you clear them.
|
||||
**Retention is off by default.** The plugin does not delete anything on its own until you explicitly
|
||||
turn the retention sweep on in the settings. Until then, stored messages stay until you clear them.
|
||||
|
||||
---
|
||||
|
||||
## What the plugin does not store
|
||||
|
||||
- Public chat (`/say`, `/yell`, `/shout`), NPC dialogue, system messages and battle logs. These are filtered before they
|
||||
reach the storage layer.
|
||||
- Anything from channels you remove from the whitelist. The privacy filter runs on the way in, not on the way out.
|
||||
- Login credentials, character IDs, account IDs. The plugin uses whatever Dalamud already exposes about the local
|
||||
character to attribute messages. Nothing of that is sent anywhere or persisted beyond the message itself.
|
||||
- Public chat (`/say`, `/yell`, `/shout`), NPC dialogue, system messages and battle logs. These are
|
||||
filtered before they reach the storage layer.
|
||||
- Anything from channels you remove from the whitelist. The privacy filter runs on the way in, not
|
||||
on the way out.
|
||||
- Login credentials, character IDs, account IDs. The plugin uses whatever Dalamud already exposes
|
||||
about the local character to attribute messages. Nothing of that is sent anywhere or persisted
|
||||
beyond the message itself.
|
||||
|
||||
---
|
||||
|
||||
## Outbound network calls
|
||||
|
||||
HellionChat makes two kinds of automatic outbound network requests. Both are inherited from upstream Chat 2 and both are
|
||||
documented here because "GDPR-by-design" means you should know what your client does on your behalf.
|
||||
HellionChat makes two kinds of automatic outbound network requests. Both are inherited from upstream
|
||||
Chat 2 and both are documented here because "GDPR-by-design" means you should know what your client
|
||||
does on your behalf.
|
||||
|
||||
### 1. BetterTTV emote service (`api.betterttv.net`, `cdn.betterttv.net`)
|
||||
|
||||
- **What it does:** When a chat message arrives that references a BetterTTV emote, the plugin asks the BetterTTV API for
|
||||
the emote metadata and downloads the image from the BetterTTV CDN to display it inline.
|
||||
- **What is sent:** A standard HTTPS GET request. Your IP address reaches BetterTTV (unavoidable for any HTTPS request);
|
||||
the request itself contains no identifying user data, no character name, no message text. Only the emote ID being
|
||||
looked up is in the URL path.
|
||||
- **What it does:** When a chat message arrives that references a BetterTTV emote, the plugin asks
|
||||
the BetterTTV API for the emote metadata and downloads the image from the BetterTTV CDN to display
|
||||
it inline.
|
||||
- **What is sent:** A standard HTTPS GET request. Your IP address reaches BetterTTV (unavoidable for
|
||||
any HTTPS request); the request itself contains no identifying user data, no character name, no
|
||||
message text. Only the emote ID being looked up is in the URL path.
|
||||
- **When it triggers:**
|
||||
- The emote _list_ (global emotes plus the top-1500 community emotes over fifteen API pages) is fetched from
|
||||
`api.betterttv.net` once per session at plugin startup, provided the **Show emotes** option is on. This first
|
||||
list-fetch happens before any chat message has arrived. BetterTTV's edge therefore sees your IP as soon as the
|
||||
plugin loads, not only after an emote is mentioned.
|
||||
- The individual emote _images_ on `cdn.betterttv.net` are fetched on demand, only when an incoming chat message
|
||||
contains a token matching one of the cached IDs. These are cached locally (`emoteCache/`) and reused across
|
||||
sessions.
|
||||
- The emote _list_ (global emotes plus the top-1500 community emotes over fifteen API pages) is
|
||||
fetched from `api.betterttv.net` once per session at plugin startup, provided the **Show
|
||||
emotes** option is on. This first list-fetch happens before any chat message has arrived.
|
||||
BetterTTV's edge therefore sees your IP as soon as the plugin loads, not only after an emote is
|
||||
mentioned.
|
||||
- The individual emote _images_ on `cdn.betterttv.net` are fetched on demand, only when an
|
||||
incoming chat message contains a token matching one of the cached IDs. These are cached locally
|
||||
(`emoteCache/`) and reused across sessions.
|
||||
- **Cached:** Yes, in `emoteCache/`. A given emote is downloaded once per machine and reused.
|
||||
- **How to opt out:** Turn off the **Show emotes** option in Settings → Chat. With it disabled, the emote cache does not
|
||||
load and no requests to BetterTTV are made for the rest of the session.
|
||||
- **How to opt out:** Turn off the **Show emotes** option in Settings → Chat. With it disabled, the
|
||||
emote cache does not load and no requests to BetterTTV are made for the rest of the session.
|
||||
- **BetterTTV's privacy policy:** <https://betterttv.com/privacy>
|
||||
|
||||
Source: `HellionChat/EmoteCache.cs`.
|
||||
@@ -94,69 +104,73 @@ Source: `HellionChat/EmoteCache.cs`.
|
||||
### 2. Square Enix Lodestone font (removed in v1.0.4)
|
||||
|
||||
Earlier versions of HellionChat (and upstream Chat 2) downloaded `FFXIV_Lodestone_SSF.ttf` from
|
||||
`img.finalfantasyxiv.com` once during font setup. That code path was a leftover from upstream's removed webinterface
|
||||
feature and was no longer consumed anywhere. The in-game symbol glyphs (job icons, item glyphs, status effects) come
|
||||
from Dalamud's bundled symbol-font helper, not from the downloaded TTF.
|
||||
`img.finalfantasyxiv.com` once during font setup. That code path was a leftover from upstream's
|
||||
removed webinterface feature and was no longer consumed anywhere. The in-game symbol glyphs (job
|
||||
icons, item glyphs, status effects) come from Dalamud's bundled symbol-font helper, not from the
|
||||
downloaded TTF.
|
||||
|
||||
The download was removed in v1.0.4. As of that version HellionChat makes no automatic network call to Square Enix or to
|
||||
any `finalfantasyxiv.com` host.
|
||||
The download was removed in v1.0.4. As of that version HellionChat makes no automatic network call
|
||||
to Square Enix or to any `finalfantasyxiv.com` host.
|
||||
|
||||
Cached `FFXIV_Lodestone_SSF.ttf` files left over from earlier versions remain in `pluginConfigs/HellionChat/` until
|
||||
manually deleted. They are no longer read.
|
||||
Cached `FFXIV_Lodestone_SSF.ttf` files left over from earlier versions remain in
|
||||
`pluginConfigs/HellionChat/` until manually deleted. They are no longer read.
|
||||
|
||||
### Links you click yourself (no automatic traffic)
|
||||
|
||||
The settings panel contains a few buttons that open external pages in your browser when you click them: the upstream
|
||||
Chat 2 GitHub repo, the upstream maintainers' Ko-fi pages, the HellionChat issue tracker and `hellion-media.de`. Nothing
|
||||
happens until you click. They are documented here for completeness, not because they generate background traffic.
|
||||
The settings panel contains a few buttons that open external pages in your browser when you click
|
||||
them: the upstream Chat 2 GitHub repo, the upstream maintainers' Ko-fi pages, the HellionChat issue
|
||||
tracker and `hellion-media.de`. Nothing happens until you click. They are documented here for
|
||||
completeness, not because they generate background traffic.
|
||||
|
||||
---
|
||||
|
||||
## What the plugin does not do
|
||||
|
||||
- **No telemetry.** Source verified: no calls to AppInsights, Sentry, PostHog, Plausible, Google Analytics, Microsoft
|
||||
Clarity or any comparable service exist in the codebase, nor in the direct dependencies the plugin pulls in. See
|
||||
`docs/THIRD_PARTY_NOTICES.md`.
|
||||
- **No crash reporting.** Crashes go to Dalamud's local `xllog`, not to a remote endpoint controlled by HellionChat.
|
||||
- **No usage counters.** The plugin does not count installs, sessions, feature usage, channel activity or anything else
|
||||
for the maintainer.
|
||||
- **No phone-home update check.** Updates are delivered through Dalamud's plugin installer, which polls the custom-repo
|
||||
`repo.json` on GitHub. That is GitHub's traffic and falls under GitHub's privacy policy. The plugin code does no
|
||||
separate update check.
|
||||
- **No background sync.** Messages stay on your machine. No cloud backup, no sharing feature, no remote viewer.
|
||||
- **No telemetry.** Source verified: no calls to AppInsights, Sentry, PostHog, Plausible, Google
|
||||
Analytics, Microsoft Clarity or any comparable service exist in the codebase, nor in the direct
|
||||
dependencies the plugin pulls in. See `docs/THIRD_PARTY_NOTICES.md`.
|
||||
- **No crash reporting.** Crashes go to Dalamud's local `xllog`, not to a remote endpoint controlled
|
||||
by HellionChat.
|
||||
- **No usage counters.** The plugin does not count installs, sessions, feature usage, channel
|
||||
activity or anything else for the maintainer.
|
||||
- **No phone-home update check.** Updates are delivered through Dalamud's plugin installer, which
|
||||
polls the custom-repo `repo.json` on GitHub. That is GitHub's traffic and falls under GitHub's
|
||||
privacy policy. The plugin code does no separate update check.
|
||||
- **No background sync.** Messages stay on your machine. No cloud backup, no sharing feature, no
|
||||
remote viewer.
|
||||
|
||||
---
|
||||
|
||||
## Your data, your rights
|
||||
|
||||
The GDPR gives you specific rights over data about you. Because HellionChat stores everything locally, those rights
|
||||
translate directly into plugin features:
|
||||
The GDPR gives you specific rights over data about you. Because HellionChat stores everything
|
||||
locally, those rights translate directly into plugin features:
|
||||
|
||||
### Right to access (Art. 15)
|
||||
|
||||
Use the export feature in the plugin settings. You can export to **Markdown**, **JSON** or **CSV**, filtered by channel,
|
||||
date range or sender substring. The export goes through a Dalamud file dialog and writes wherever you point it, on your
|
||||
machine.
|
||||
Use the export feature in the plugin settings. You can export to **Markdown**, **JSON** or **CSV**,
|
||||
filtered by channel, date range or sender substring. The export goes through a Dalamud file dialog
|
||||
and writes wherever you point it, on your machine.
|
||||
|
||||
### Right to erasure (Art. 17)
|
||||
|
||||
Two options:
|
||||
|
||||
1. **Targeted deletion.** The "retroactive cleanup" feature lets you apply your current whitelist to the existing
|
||||
database. It shows a preview of what will be removed before you confirm with Ctrl+Shift, runs in the background, and
|
||||
calls `VACUUM` afterwards to actually shrink the file.
|
||||
2. **Full deletion.** Close the game and delete the `pluginConfigs/HellionChat/` directory. The next plugin start will
|
||||
produce a fresh, empty configuration.
|
||||
1. **Targeted deletion.** The "retroactive cleanup" feature lets you apply your current whitelist to
|
||||
the existing database. It shows a preview of what will be removed before you confirm with
|
||||
Ctrl+Shift, runs in the background, and calls `VACUUM` afterwards to actually shrink the file.
|
||||
2. **Full deletion.** Close the game and delete the `pluginConfigs/HellionChat/` directory. The next
|
||||
plugin start will produce a fresh, empty configuration.
|
||||
|
||||
### Right to portability (Art. 20)
|
||||
|
||||
The JSON and CSV exports are open formats. The Markdown export is human-readable and machine-parseable. Nothing is
|
||||
locked into a proprietary container.
|
||||
The JSON and CSV exports are open formats. The Markdown export is human-readable and
|
||||
machine-parseable. Nothing is locked into a proprietary container.
|
||||
|
||||
### Right to object / restrict processing (Art. 21, 18)
|
||||
|
||||
Adjust the channel whitelist or set retention to a low value. Both take effect immediately on new messages. Existing
|
||||
data needs the retroactive cleanup to apply retroactively, by design.
|
||||
Adjust the channel whitelist or set retention to a low value. Both take effect immediately on new
|
||||
messages. Existing data needs the retroactive cleanup to apply retroactively, by design.
|
||||
|
||||
---
|
||||
|
||||
@@ -168,32 +182,34 @@ data needs the retroactive cleanup to apply retroactively, by design.
|
||||
| Hellion Forge (Gitea, self-hosted by Hellion Online Media) | Plugin distribution via custom repo, issue tracker | Whatever the Gitea instance sees from any HTTPS request to a public repo | <https://hellion-media.de/datenschutz> |
|
||||
| Dalamud / XIVLauncher (goatcorp) | Plugin loader, font subsystem, repo polling | Whatever Dalamud reports for itself; out of HellionChat's scope | <https://github.com/goatcorp/Dalamud> |
|
||||
|
||||
The Hellion Forge Gitea instance and the Dalamud/XIVLauncher loader are unavoidable for anyone using HellionChat through
|
||||
Dalamud at all. BetterTTV is the only third party HellionChat introduces on top of that baseline, and it is opt-out via
|
||||
settings.
|
||||
The Hellion Forge Gitea instance and the Dalamud/XIVLauncher loader are unavoidable for anyone using
|
||||
HellionChat through Dalamud at all. BetterTTV is the only third party HellionChat introduces on top
|
||||
of that baseline, and it is opt-out via settings.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies that touch the network
|
||||
|
||||
For a full dependency inventory see `docs/THIRD_PARTY_NOTICES.md`. Of the direct dependencies the plugin pulls in:
|
||||
For a full dependency inventory see `docs/THIRD_PARTY_NOTICES.md`. Of the direct dependencies the
|
||||
plugin pulls in:
|
||||
|
||||
- `MessagePack`: local serialisation, no network.
|
||||
- `Microsoft.Data.Sqlite`: local SQLite access, no network.
|
||||
- `morelinq`: LINQ helpers, no network.
|
||||
- `Pidgin`: parser combinators, no network.
|
||||
- `SixLabors.ImageSharp`: image decoding (used for the BetterTTV emote pipeline), no network on its own.
|
||||
- `SixLabors.ImageSharp`: image decoding (used for the BetterTTV emote pipeline), no network on its
|
||||
own.
|
||||
|
||||
The single network call listed under "Outbound network calls" is written directly in HellionChat's own source, not
|
||||
delegated to a dependency.
|
||||
The single network call listed under "Outbound network calls" is written directly in HellionChat's
|
||||
own source, not delegated to a dependency.
|
||||
|
||||
---
|
||||
|
||||
## Changes to this notice
|
||||
|
||||
If a future release changes what HellionChat stores, sends or caches, this document will be updated and the change
|
||||
called out in the changelog block of that release. The "Last reviewed" date at the top tracks the version this document
|
||||
is accurate for.
|
||||
If a future release changes what HellionChat stores, sends or caches, this document will be updated
|
||||
and the change called out in the changelog block of that release. The "Last reviewed" date at the
|
||||
top tracks the version this document is accurate for.
|
||||
|
||||
---
|
||||
|
||||
@@ -204,10 +220,10 @@ For privacy-related questions specific to HellionChat:
|
||||
- Email: `kontakt@hellion-media.de`
|
||||
- Discord DM: `@j.j_kazama`
|
||||
|
||||
Security-relevant findings (for example, the plugin storing or sending something this document says it does not) go
|
||||
through the private advisory in `SECURITY.md`, not a public issue.
|
||||
Security-relevant findings (for example, the plugin storing or sending something this document says
|
||||
it does not) go through the private advisory in `SECURITY.md`, not a public issue.
|
||||
|
||||
---
|
||||
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
|
||||
[hellion-media.de](https://hellion-media.de)
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
|
||||
Harzburg | [hellion-media.de](https://hellion-media.de)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/actions/workflows/build.yml)
|
||||
[](LICENSE)
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||
[](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/latest)
|
||||
[](https://github.com/goatcorp/Dalamud)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.finalfantasyxiv.com/)
|
||||
@@ -11,30 +11,33 @@
|
||||
<img src="docs/images/hellion-forge.png" alt="Hellion Forge" width="180" />
|
||||
</p>
|
||||
|
||||
**Version 1.4.8** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
||||
**Version 1.5.1** — Privacy-first chat plugin for FINAL FANTASY XIV / Dalamud, built on
|
||||
[Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
||||
|
||||
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine comes from Chat 2
|
||||
(message store, channel logic, hook system), and most keyboard shortcuts continue to work as you'd expect. What changes:
|
||||
stricter privacy defaults out of the box, custom slash commands under `/hellionchat`, no web interface, and as of
|
||||
v1.1.0, a theme engine as a step toward a distinct UI look and feel.
|
||||
Hellion Chat is a privacy-first plugin built on the Chat 2 foundation. The majority of the engine
|
||||
comes from Chat 2 (message store, channel logic, hook system), and most keyboard shortcuts continue
|
||||
to work as you'd expect. What changes: stricter privacy defaults out of the box, custom slash
|
||||
commands under `/hellionchat`, no web interface, and as of v1.1.0, a theme engine as a step toward a
|
||||
distinct UI look and feel.
|
||||
|
||||
The data-handling focus is on GDPR/EU, US, and JP regulations, as far as practically applicable for a chat plugin:
|
||||
per-channel retention periods, granular filters, and self-service data export. A full breakdown is available in
|
||||
[`PRIVACY.md`](PRIVACY.md).
|
||||
The data-handling focus is on GDPR/EU, US, and JP regulations, as far as practically applicable for
|
||||
a chat plugin: per-channel retention periods, granular filters, and self-service data export. A full
|
||||
breakdown is available in [`PRIVACY.md`](PRIVACY.md).
|
||||
|
||||
This is a standalone repository, licensed under EUPL-1.2. With v1.0.0 the standalone cut is complete: own namespace
|
||||
`HellionChat.*`, own IPC channels, own source tree structure. Distribution via custom repo. Active upstream sync ended
|
||||
with the v1.4.x cycle: Chat 2 is undergoing a fundamental rework and cherry-picks are no longer portable. From here,
|
||||
Hellion Chat continues as an independent codebase — background and attribution in
|
||||
This is a standalone repository, licensed under EUPL-1.2. With v1.0.0 the standalone cut is
|
||||
complete: own namespace `HellionChat.*`, own IPC channels, own source tree structure. Distribution
|
||||
via custom repo. Active upstream sync ended with the v1.4.x cycle: Chat 2 is undergoing a
|
||||
fundamental rework and cherry-picks are no longer portable. From here, Hellion Chat continues as an
|
||||
independent codebase — background and attribution in
|
||||
[`docs/UPSTREAM_SYNC.md`](docs/UPSTREAM_SYNC.md).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Hellion Chat is built on [Chat 2](https://github.com/Infiziert90/ChatTwo) by
|
||||
**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and **[Anna](https://github.com/anna-is-cute)**, who maintained
|
||||
the plugin for years before I ever saw the source code. The entire core architecture, the message store, the channel
|
||||
logic, the hook system, and much more all come from them. If Hellion Chat helps you, the credit for that belongs in
|
||||
**[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
|
||||
**[Anna](https://github.com/anna-is-cute)**, who maintained the plugin for years before I ever saw
|
||||
the source code. The entire core architecture, the message store, the channel logic, the hook
|
||||
system, and much more all come from them. If Hellion Chat helps you, the credit for that belongs in
|
||||
large part to Infi and Anna. A full acknowledgement is in [NOTICE.md](NOTICE.md).
|
||||
|
||||
Hellion Chat is developed under **Hellion Forge**, the specialized modding and plugin branch of
|
||||
@@ -62,67 +65,73 @@ Hellion Chat is developed under **Hellion Forge**, the specialized modding and p
|
||||
|
||||
### Privacy / Compliance
|
||||
|
||||
- **Channel whitelist** for database persistence with privacy-first defaults. Out of the box, only your own
|
||||
conversations are stored (tells, party, FC, linkshells, cross-world linkshells, alliance, ExtraChat). Public chat, NPC
|
||||
dialogue, system spam, and battle logs are discarded at the storage layer.
|
||||
- **Per-channel retention periods** with a daily background cleanup. Tells: 365 days, own conversation channels: 90
|
||||
days, global default: 30 days. The default is OFF — the plugin deletes nothing without explicit consent.
|
||||
- **Retroactive cleanup** with preview and Ctrl+Shift confirmation. Applies the current whitelist to an existing
|
||||
database, runs in the background, and calls VACUUM afterward.
|
||||
- **Export** to Markdown, JSON, or CSV via the Dalamud file dialog (GDPR Art. 15 right of access). Filter by channel,
|
||||
date range, or sender substring.
|
||||
- **Channel whitelist** for database persistence with privacy-first defaults. Out of the box, only
|
||||
your own conversations are stored (tells, party, FC, linkshells, cross-world linkshells, alliance,
|
||||
ExtraChat). Public chat, NPC dialogue, system spam, and battle logs are discarded at the storage
|
||||
layer.
|
||||
- **Per-channel retention periods** with a daily background cleanup. Tells: 365 days, own
|
||||
conversation channels: 90 days, global default: 30 days. The default is OFF — the plugin deletes
|
||||
nothing without explicit consent.
|
||||
- **Retroactive cleanup** with preview and Ctrl+Shift confirmation. Applies the current whitelist to
|
||||
an existing database, runs in the background, and calls VACUUM afterward.
|
||||
- **Export** to Markdown, JSON, or CSV via the Dalamud file dialog (GDPR Art. 15 right of access).
|
||||
Filter by channel, date range, or sender substring.
|
||||
- **Full privacy overview** in [`PRIVACY.md`](PRIVACY.md) and third-party components in
|
||||
[`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md): what is stored, which two outbound calls exist
|
||||
(BetterTTV opt-out, Square Enix Lodestone font), an explicit no-telemetry statement, and the mapping of GDPR rights
|
||||
(Art. 15/17/18/20/21) to concrete plugin functions.
|
||||
[`docs/THIRD_PARTY_NOTICES.md`](docs/THIRD_PARTY_NOTICES.md): what is stored, which two outbound
|
||||
calls exist (BetterTTV opt-out, Square Enix Lodestone font), an explicit no-telemetry statement,
|
||||
and the mapping of GDPR rights (Art. 15/17/18/20/21) to concrete plugin functions.
|
||||
|
||||
### Onboarding
|
||||
|
||||
- **First-run wizard** with three profiles (Privacy-First, Relaxed, Full History) and a GDPR notice for the "Full
|
||||
History" option.
|
||||
- **Configuration migration** seeds privacy defaults for existing users and shows a notification on the first plugin
|
||||
start after an update. With v1.0.0, users on config version 12 or older also receive a one-time tab layout reset; the
|
||||
old tab configuration is backed up as `pluginConfigs/HellionChat.json.pre-v13-backup`.
|
||||
- **Layout migration from Chat 2** moves configuration and database to `pluginConfigs/HellionChat/` without data loss.
|
||||
Handles locked files gracefully (warns the user if Chat 2 is still loaded).
|
||||
- **First-run wizard** with three profiles (Privacy-First, Relaxed, Full History) and a GDPR notice
|
||||
for the "Full History" option.
|
||||
- **Configuration migration** seeds privacy defaults for existing users and shows a notification on
|
||||
the first plugin start after an update. With v1.0.0, users on config version 12 or older also
|
||||
receive a one-time tab layout reset; the old tab configuration is backed up as
|
||||
`pluginConfigs/HellionChat.json.pre-v13-backup`.
|
||||
- **Layout migration from Chat 2** moves configuration and database to `pluginConfigs/HellionChat/`
|
||||
without data loss. Handles locked files gracefully (warns the user if Chat 2 is still loaded).
|
||||
- **Migrate3 recovery** heals partially migrated databases from old Chat 2 installations.
|
||||
|
||||
### Look & Feel
|
||||
|
||||
- **Bilingual UI** (English and German) with live language switching. Hellion-specific strings are in
|
||||
`HellionStrings.<lang>.resx`.
|
||||
- **Hellion HUD theme** with cyan-teal accents, slate-violet tabs, and amber highlights for active states.
|
||||
- **Chat color presets** (v0.6.0) with seven built-in bundles in Settings → Appearance → Chat Colors: Classic (Chat 2
|
||||
default), High Contrast, Pastel, Dark Mode Tuned, Hellion (brand), plus bonus moods Night Blue and Indigo Violet.
|
||||
One-click apply, battle channels remain untouched.
|
||||
- **Bilingual UI** (English and German) with live language switching. Hellion-specific strings are
|
||||
in `HellionStrings.<lang>.resx`.
|
||||
- **Hellion HUD theme** with cyan-teal accents, slate-violet tabs, and amber highlights for active
|
||||
states.
|
||||
- **Chat color presets** (v0.6.0) with seven built-in bundles in Settings → Appearance → Chat
|
||||
Colors: Classic (Chat 2 default), High Contrast, Pastel, Dark Mode Tuned, Hellion (brand), plus
|
||||
bonus moods Night Blue and Indigo Violet. One-click apply, battle channels remain untouched.
|
||||
- **Window opacity slider** for combat-friendly transparency.
|
||||
- **Bundled Hellion font** (Exo 2, OFL-1.1) as an optional default instead of the system font.
|
||||
- **Hellion logo** bundled in the plugin and displayed in the Dalamud plugin list.
|
||||
|
||||
#### Custom Themes (v1.1.0)
|
||||
|
||||
HellionChat ships a theme engine with ten built-in themes (Hellion Arctic, Hellion Spectrum, Chat 2 Classic, Event
|
||||
Horizon, Crystal Nocturne, Mint Grove, Night Blue, Indigo Violet, Forge Merchantman, Synthwave Sunset) and a JSON-based
|
||||
authoring format for custom themes. Schema and step-by-step guide in
|
||||
[`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md). Hellion Spectrum is Deuteranopia/Protanopia-safe (red-green color
|
||||
blindness) based on the Wong/Okabe-Ito palette.
|
||||
HellionChat ships a theme engine with ten built-in themes (Hellion Arctic, Hellion Spectrum, Chat 2
|
||||
Classic, Event Horizon, Crystal Nocturne, Mint Grove, Night Blue, Indigo Violet, Forge Merchantman,
|
||||
Synthwave Sunset) and a JSON-based authoring format for custom themes. Schema and step-by-step guide
|
||||
in [`docs/THEME-AUTHORING.md`](docs/THEME-AUTHORING.md). Hellion Spectrum is
|
||||
Deuteranopia/Protanopia-safe (red-green color blindness) based on the Wong/Okabe-Ito palette.
|
||||
|
||||
#### Plugin Integrations (v1.3.0)
|
||||
|
||||
- **Honorific custom titles in the chat header.** When the Honorific plugin is active and a custom title is set, it is
|
||||
displayed in the chat header above the message log. Auto-detect with silent fallback: without Honorific the slot is
|
||||
invisible. Toggle in Settings → Integrations → Honorific. First cycle of a multi-stage plugin integration roadmap
|
||||
(context menu, NotificationMaster, RP status, ExtraChat, and XIVIM to follow).
|
||||
- **Honorific custom titles in the chat header.** When the Honorific plugin is active and a custom
|
||||
title is set, it is displayed in the chat header above the message log. Auto-detect with silent
|
||||
fallback: without Honorific the slot is invisible. Toggle in Settings → Integrations → Honorific.
|
||||
First cycle of a multi-stage plugin integration roadmap (context menu, NotificationMaster, RP
|
||||
status, ExtraChat, and XIVIM to follow).
|
||||
|
||||
### Pop-Out Convenience (v0.6.0)
|
||||
|
||||
- **Input bar in pop-out windows** as a global opt-in in Settings → Windows → Window Frame. When active, every pop-out
|
||||
window has a compact input at the bottom with a channel-colored icon button and text field. No more switching back to
|
||||
the main window for a quick reply.
|
||||
- **Per-pop-out independent text buffer and history cursor.** Changing channels in a pop-out works globally like in the
|
||||
main window (FFXIV channel API), but half-typed input doesn't collide between the main window and pop-outs.
|
||||
- **Shared input history** across all windows via singleton service. Up/Down arrow keys navigate the same list of the
|
||||
last 30 entries everywhere.
|
||||
- **Input bar in pop-out windows** as a global opt-in in Settings → Windows → Window Frame. When
|
||||
active, every pop-out window has a compact input at the bottom with a channel-colored icon button
|
||||
and text field. No more switching back to the main window for a quick reply.
|
||||
- **Per-pop-out independent text buffer and history cursor.** Changing channels in a pop-out works
|
||||
globally like in the main window (FFXIV channel API), but half-typed input doesn't collide between
|
||||
the main window and pop-outs.
|
||||
- **Shared input history** across all windows via singleton service. Up/Down arrow keys navigate the
|
||||
same list of the last 30 entries everywhere.
|
||||
|
||||
### Stability
|
||||
|
||||
@@ -132,12 +141,13 @@ blindness) based on the Wong/Okabe-Ito palette.
|
||||
|
||||
### What's missing compared to Chat 2
|
||||
|
||||
The web interface was removed in Hellion Chat 0.2.0. It serves a different use case than the focus of this fork — remote
|
||||
access to chat from a second device — which conflicts directly with the privacy-first premise: a chat plugin that starts
|
||||
a local HTTP server is too large an attack surface for my threat model. So it's gone.
|
||||
The web interface was removed in Hellion Chat 0.2.0. It serves a different use case than the focus
|
||||
of this fork — remote access to chat from a second device — which conflicts directly with the
|
||||
privacy-first premise: a chat plugin that starts a local HTTP server is too large an attack surface
|
||||
for my threat model. So it's gone.
|
||||
|
||||
If you want the full Chat 2 feature set, the upstream plugin serves you better. Hellion Chat is deliberately the slimmer
|
||||
fork.
|
||||
If you want the full Chat 2 feature set, the upstream plugin serves you better. Hellion Chat is
|
||||
deliberately the slimmer fork.
|
||||
|
||||
---
|
||||
|
||||
@@ -172,24 +182,26 @@ HellionChat/
|
||||
|
||||
### Rules
|
||||
|
||||
- **Code namespace is `HellionChat.*`.** Fully consolidated onto the plugin name since v1.0.0 — no remaining `ChatTwo.*`
|
||||
anywhere in the source tree.
|
||||
- **AssemblyName is `HellionChat`.** Own slot in `pluginConfigs/`, own file manifest, no shared state with Chat 2.
|
||||
Parallel-loading upstream Chat 2 is actively blocked on startup (bilingual conflict message).
|
||||
- **IPC channels are `HellionChat.*`.** Six channels for third-party plugin integration (`Register`, `Available`,
|
||||
`Unregister`, `Invoke`, `GetChatInputState`, `ChatInputStateChanged`). Details in [`docs/IPC.md`](docs/IPC.md).
|
||||
- **Hellion-specific strings in `HellionStrings.*.resx`**, strings carried over from Chat 2 in `Language.*.resx`. The
|
||||
original `Language.*.resx` structure is preserved because the existing translations from the upstream Crowdin
|
||||
community remain valuable.
|
||||
- **No direct access to `Plugin.Interface.UiBuilder.FontAtlas`** outside of `FontManager`. Font fallback and the Hellion
|
||||
font are managed centrally.
|
||||
- **Code namespace is `HellionChat.*`.** Fully consolidated onto the plugin name since v1.0.0 — no
|
||||
remaining `ChatTwo.*` anywhere in the source tree.
|
||||
- **AssemblyName is `HellionChat`.** Own slot in `pluginConfigs/`, own file manifest, no shared
|
||||
state with Chat 2. Parallel-loading upstream Chat 2 is actively blocked on startup (bilingual
|
||||
conflict message).
|
||||
- **IPC channels are `HellionChat.*`.** Six channels for third-party plugin integration (`Register`,
|
||||
`Available`, `Unregister`, `Invoke`, `GetChatInputState`, `ChatInputStateChanged`). Details in
|
||||
[`docs/IPC.md`](docs/IPC.md).
|
||||
- **Hellion-specific strings in `HellionStrings.*.resx`**, strings carried over from Chat 2 in
|
||||
`Language.*.resx`. The original `Language.*.resx` structure is preserved because the existing
|
||||
translations from the upstream Crowdin community remain valuable.
|
||||
- **No direct access to `Plugin.Interface.UiBuilder.FontAtlas`** outside of `FontManager`. Font
|
||||
fallback and the Hellion font are managed centrally.
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
SQLite, schema inherited from upstream Chat 2 (migration level v3). Hellion extensions live in `Configuration` as
|
||||
fields, not in the DB schema:
|
||||
SQLite, schema inherited from upstream Chat 2 (migration level v3). Hellion extensions live in
|
||||
`Configuration` as fields, not in the DB schema:
|
||||
|
||||
| Column | Type | Description |
|
||||
| ---------------- | ------- | ---------------------------- |
|
||||
@@ -229,15 +241,15 @@ Hellion Chat is distributed via a Dalamud **custom repository**.
|
||||
|
||||
### Migration from Chat 2 (with existing history)
|
||||
|
||||
Chat 2 and Hellion Chat share the database file until Hellion Chat moves it to its own path on first start. Order
|
||||
matters:
|
||||
Chat 2 and Hellion Chat share the database file until Hellion Chat moves it to its own path on first
|
||||
start. Order matters:
|
||||
|
||||
1. **Disable Chat 2** in `/xlplugins` (do not uninstall, just disable).
|
||||
2. **Fully close FFXIV** so SQLite releases the file lock. A plugin reload alone is not enough.
|
||||
3. Restart the game.
|
||||
4. Add the custom repo as described above.
|
||||
5. Install Hellion Chat. On first start, the configuration file and the entire database directory are moved into the
|
||||
HellionChat layout.
|
||||
5. Install Hellion Chat. On first start, the configuration file and the entire database directory
|
||||
are moved into the HellionChat layout.
|
||||
6. **Verify** under Settings → Privacy → Refresh preview that the message count looks plausible.
|
||||
|
||||
### Troubleshooting
|
||||
@@ -267,17 +279,18 @@ Start the game, enable Hellion Chat, and your history is back.
|
||||
|
||||
### Updates
|
||||
|
||||
Updates appear automatically in the plugin list once a new `vX.Y.Z` tag with a GitHub Release is published. No reinstall
|
||||
needed.
|
||||
Updates appear automatically in the plugin list once a new `vX.Y.Z` tag with a GitHub Release is
|
||||
published. No reinstall needed.
|
||||
|
||||
---
|
||||
|
||||
## Distribution
|
||||
|
||||
Hellion Chat is distributed via its own Dalamud custom repository (`repo.json` in the repo root). Pushing a `vX.Y.Z` tag
|
||||
triggers the [`release.yml`](.github/workflows/release.yml) workflow, which attaches the build output
|
||||
(`HellionChat/bin/Release/HellionChat/latest.zip`) along with the matching changelog block from `HellionChat.yaml` to
|
||||
the GitHub Release. Manual recovery path if the auto-trigger is missed: `gh workflow run release.yml -f tag=vX.Y.Z`.
|
||||
Hellion Chat is distributed via its own Dalamud custom repository (`repo.json` in the repo root).
|
||||
Pushing a `vX.Y.Z` tag triggers the [`release.yml`](.github/workflows/release.yml) workflow, which
|
||||
attaches the build output (`HellionChat/bin/Release/HellionChat/latest.zip`) along with the matching
|
||||
changelog block from `HellionChat.yaml` to the GitHub Release. Manual recovery path if the
|
||||
auto-trigger is missed: `gh workflow run release.yml -f tag=vX.Y.Z`.
|
||||
|
||||
An optional submission to the Dalamud main plugin repo (in addition to the custom repo) is on the
|
||||
[roadmap](docs/ROADMAP.md).
|
||||
@@ -286,19 +299,45 @@ An optional submission to the Dalamud main plugin repo (in addition to the custo
|
||||
|
||||
## Project Status
|
||||
|
||||
**Version 1.4.8** — Hook-Layer and Polish Quick-Wins. The Database Viewer now has an optional FTS5 full-text search
|
||||
across the entire chat history. Toggle "Full-text search" next to the search bar; the index is built asynchronously on
|
||||
first run after the update with a progress toast, and the toggle stays disabled until the build completes. Multi-word
|
||||
terms match as exact phrases by default; power users can opt into raw FTS5 `MATCH` syntax by wrapping their own
|
||||
double-quotes. Custom theme files auto-reload when edited while the theme is active — save the JSON in your editor and
|
||||
the live render picks up the change within a second, no picker click. Retention sweep no longer blocks the framework
|
||||
thread (`Framework.Run(...).Wait()` replaced by `Framework.RunOnTick(...)`), removing the ~194 ms hitch per sweep. Status
|
||||
bar height is now derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders correctly at
|
||||
Windows display scaling above 100 %. Receive-suppressed-tells routing is postponed to v1.5.x; the investigation in this
|
||||
cycle showed that the FFXIV `ContentIdResolverHook` does not fire when other plugins suppress tells via
|
||||
`CheckMessageHandled`, which means tell-partner identification breaks for AutoTellTab routing — the fix lives next to
|
||||
the planned ad-block hook layer where the same `RaptureLogModule` patch surface comes up anyway. Migration v17 stays
|
||||
(no schema bump). Ninth sub-patch of the v1.4.x polish sweep series (as of 2026-05-14).
|
||||
**Version 1.5.1** — FontAtlas Refactor and Hellion Forge Signature. The FontManager moves from the
|
||||
inherited Chat 2 anti-pattern (null! fields + a separate BuildFonts method) to a hybrid model where
|
||||
the game fonts and FontAwesome are init-only handles and only the user-configurable delegate fonts
|
||||
stay mutable. All five handles register inside a single `IFontAtlas.SuppressAutoRebuild` block in
|
||||
the ctor, so the font atlas builds once per plugin load instead of four to five times. FontAwesome
|
||||
itself is now Dalamud's `UiBuilder.IconFontFixedWidthHandle` rather than a custom delegate slot.
|
||||
Live font-setting changes go through `RebuildDelegateFonts()` and no longer require a plugin reload.
|
||||
Plus an embedded Hellion Forge signature (drawn by Julia Moon) appears as a fox-head silhouette in
|
||||
the `/xllog` bootstrap banner and a full fox in the First-Run Wizard and Settings → Information tab.
|
||||
|
||||
The Honorific full-gradient port that was originally scheduled for v1.5.1 was dropped: Honorific 3.2
|
||||
exposes no IPC for the rendered gradient frame, and an in-plugin port of the colour palette was
|
||||
declined. The integration stays at the v1.4.7 glow-only shape.
|
||||
|
||||
The 10× HITCH cut targeted from the v1.5.0 cross-plugin baseline (Lightless / XIVInstantMessenger
|
||||
defer their font-atlas build to land at ~7 ms; Chat 2 + HellionChat were ~75 ms) did not land — the
|
||||
cost lives in the UiBuilder first-frame render path, not in the atlas build. A first-frame render
|
||||
investigation is reserved for a later cycle.
|
||||
|
||||
---
|
||||
|
||||
### Project status (pre-v1.5.1, kept for context)
|
||||
|
||||
**Version 1.5.0** — DI Foundation and Service Refactor. Major architecture cycle: the plugin
|
||||
bootstrap moves to a generic-host DI container (`Microsoft.Extensions.Hosting` +
|
||||
`IServiceCollection`) modelled on Lightless Sync. All 18 instance-class services migrate from a
|
||||
static `Plugin.LogProxy` locator to `Microsoft.Extensions.Logging.ILogger<T>` via constructor
|
||||
injection, with a custom `DalamudLogger` bridging the framework over to Dalamud's `IPluginLog`. The
|
||||
proxy stays for the eight buckets ctor-injection cannot reach (static helpers like `EmoteCache`,
|
||||
Dalamud-reflected `Configuration`, the `Message` data class, and static methods inside `FontManager`
|
||||
/ `GameFunctions`). Plugin.cs finishes the cycle at 1012 lines — virtually identical to the
|
||||
pre-cycle 1013 — because the new Phase-1 host build and Plugin.X bridge wiring trade out exactly the
|
||||
service and window allocations that left `LoadAsync`. Cross-plugin baseline confirms no performance
|
||||
penalty vs Chat 2: HellionChat first-frame HITCH 77 ms median, Chat 2 74 ms. Lightless and
|
||||
XIVInstantMessenger sit around 7 ms by deferring their font-atlas build past `Finished loading` —
|
||||
that pattern is the v1.5.1 follow-up item. One user-visible fix bundled in from upstream: pasting a
|
||||
slash command into the chat input (Friend List "/tell" action, plugin-driven inserts) now replaces
|
||||
the existing input instead of concatenating onto whatever the user was typing. Migration v17 stays
|
||||
(no schema bump).
|
||||
|
||||
Hellion Chat is a standalone plugin, no longer a fork in the repository sense. Fully completed:
|
||||
|
||||
@@ -313,29 +352,31 @@ Hellion Chat is a standalone plugin, no longer a fork in the repository sense. F
|
||||
- Audit hardening (path traversal, retention race, DbViewer consistency)
|
||||
- About tab in Hellion branding, localized EN and DE, with license and disclaimer
|
||||
- AI disclosure documented (see [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md))
|
||||
- Standalone cut: namespace `HellionChat.*`, IPC channels `HellionChat.*`, source tree restructure, conflict detection
|
||||
against upstream Chat 2, SQLite CVE hardening (3.50.3)
|
||||
- Theme engine with ten built-in themes plus JSON authoring format (engine v1.1.0, catalog extended in v1.2.3, including
|
||||
CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
|
||||
- ABGR cache on theme records: `HellionStyle.PushGlobal` reads pre-computed ABGR instead of converting RGBA→ABGR per
|
||||
slot per frame (v1.4.1, ~13% render-time recovery)
|
||||
- Standalone cut: namespace `HellionChat.*`, IPC channels `HellionChat.*`, source tree restructure,
|
||||
conflict detection against upstream Chat 2, SQLite CVE hardening (3.50.3)
|
||||
- Theme engine with ten built-in themes plus JSON authoring format (engine v1.1.0, catalog extended
|
||||
in v1.2.3, including CVD-safe Hellion Spectrum; Synthwave Sunset in v1.4.1)
|
||||
- ABGR cache on theme records: `HellionStyle.PushGlobal` reads pre-computed ABGR instead of
|
||||
converting RGBA→ABGR per slot per frame (v1.4.1, ~13% render-time recovery)
|
||||
|
||||
In progress: incremental modernization of UI look and feel beyond the theme engine. What's planned next and what's on
|
||||
the long-term list is in [`docs/ROADMAP.md`](docs/ROADMAP.md). Concrete scheduled items are also tracked in the
|
||||
[Gitea issue tracker](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) under the `roadmap` label.
|
||||
In progress: incremental modernization of UI look and feel beyond the theme engine. What's planned
|
||||
next and what's on the long-term list is in [`docs/ROADMAP.md`](docs/ROADMAP.md). Concrete scheduled
|
||||
items are also tracked in the
|
||||
[Gitea issue tracker](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues) under
|
||||
the `roadmap` label.
|
||||
|
||||
### On Release Cadence
|
||||
|
||||
Anyone looking at the repo for the first time will notice a lot of releases and a high commit count in a short time.
|
||||
Both are deliberate. The full reasoning — four factors behind it — is in
|
||||
Anyone looking at the repo for the first time will notice a lot of releases and a high commit count
|
||||
in a short time. Both are deliberate. The full reasoning — four factors behind it — is in
|
||||
[`docs/LEARNING-JOURNEY.md`](docs/LEARNING-JOURNEY.md), section "How I release this fast".
|
||||
|
||||
---
|
||||
|
||||
## Community & Support
|
||||
|
||||
- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and tools):
|
||||
[discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
|
||||
- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and
|
||||
tools): [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
|
||||
- Bug reports and feature requests:
|
||||
[Gitea Issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues)
|
||||
- Discord DM: `@j.j_kazama`
|
||||
@@ -345,25 +386,26 @@ Both are deliberate. The full reasoning — four factors behind it — is in
|
||||
|
||||
## License
|
||||
|
||||
EUPL-1.2 (same license as upstream Chat 2). Full text in [LICENSE](LICENSE), copyright notes with dual-holder block in
|
||||
[COPYRIGHT](COPYRIGHT), personal acknowledgement to the upstream authors in [NOTICE.md](NOTICE.md).
|
||||
EUPL-1.2 (same license as upstream Chat 2). Full text in [LICENSE](LICENSE), copyright notes with
|
||||
dual-holder block in [COPYRIGHT](COPYRIGHT), personal acknowledgement to the upstream authors in
|
||||
[NOTICE.md](NOTICE.md).
|
||||
|
||||
© 2023–2026 the Chat 2 authors ([Infi](https://github.com/Infiziert90), [Anna](https://github.com/anna-is-cute), and
|
||||
upstream contributors) for the engine, IPC, and storage layer. © 2026 Hellion Online Media for the Hellion Chat
|
||||
extensions.
|
||||
© 2023–2026 the Chat 2 authors ([Infi](https://github.com/Infiziert90),
|
||||
[Anna](https://github.com/anna-is-cute), and upstream contributors) for the engine, IPC, and storage
|
||||
layer. © 2026 Hellion Online Media for the Hellion Chat extensions.
|
||||
|
||||
### Acknowledgments
|
||||
|
||||
- **[Infi](https://github.com/Infiziert90) and [Anna](https://github.com/anna-is-cute) (ascclemens)** for the Chat 2
|
||||
engine, without which this fork would not exist.
|
||||
- **[Infi](https://github.com/Infiziert90) and [Anna](https://github.com/anna-is-cute)
|
||||
(ascclemens)** for the Chat 2 engine, without which this fork would not exist.
|
||||
- **Dalamud team** for the plugin framework.
|
||||
- **Chat 2 Crowdin community** for the upstream string translations (see Settings → Info → "Chat 2 community
|
||||
translators").
|
||||
- **Chat 2 Crowdin community** for the upstream string translations (see Settings → Info → "Chat 2
|
||||
community translators").
|
||||
|
||||
### FFXIV Disclaimer
|
||||
|
||||
FINAL FANTASY XIV © SQUARE ENIX CO., LTD. All rights reserved. Hellion Chat is an unofficial, fan-made plugin and is not
|
||||
affiliated with, supported by, sponsored by, or approved by Square Enix.
|
||||
FINAL FANTASY XIV © SQUARE ENIX CO., LTD. All rights reserved. Hellion Chat is an unofficial,
|
||||
fan-made plugin and is not affiliated with, supported by, sponsored by, or approved by Square Enix.
|
||||
|
||||
### AI Assistance
|
||||
|
||||
@@ -373,7 +415,8 @@ See [`docs/AI_DISCLOSURE.md`](docs/AI_DISCLOSURE.md) for the pair-level disclosu
|
||||
|
||||
## Project Documents
|
||||
|
||||
Standard repository documents live in the repo root; deeper documentation lives under [`docs/`](docs/).
|
||||
Standard repository documents live in the repo root; deeper documentation lives under
|
||||
[`docs/`](docs/).
|
||||
|
||||
### Repo Root
|
||||
|
||||
@@ -404,5 +447,5 @@ Standard repository documents live in the repo root; deeper documentation lives
|
||||
|
||||
---
|
||||
|
||||
Developed under **Hellion Forge**, the modding and plugin branch of **Hellion Online Media** | Bad Harzburg |
|
||||
[hellion-media.de](https://hellion-media.de)
|
||||
Developed under **Hellion Forge**, the modding and plugin branch of **Hellion Online Media** | Bad
|
||||
Harzburg | [hellion-media.de](https://hellion-media.de)
|
||||
|
||||
+8
-7
@@ -2,8 +2,8 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a security issue in HellionChat, please do not open a public Gitea issue. Use one of the private channels
|
||||
below so I can investigate and ship a fix before the details go public.
|
||||
If you find a security issue in HellionChat, please do not open a public Gitea issue. Use one of the
|
||||
private channels below so I can investigate and ship a fix before the details go public.
|
||||
|
||||
**Preferred:**
|
||||
|
||||
@@ -20,7 +20,8 @@ I respond on weekdays during European business hours.
|
||||
|
||||
### In scope
|
||||
|
||||
- Code paths that touch user-controlled input (chat messages, plugin config, file paths the user can influence)
|
||||
- Code paths that touch user-controlled input (chat messages, plugin config, file paths the user can
|
||||
influence)
|
||||
- The privacy filter in `MessageStore.cs` and the export pipeline
|
||||
- The configuration migration logic
|
||||
- The `EmoteCache` HTTP client and path handling
|
||||
@@ -36,10 +37,10 @@ I respond on weekdays during European business hours.
|
||||
|
||||
## Disclosure Window
|
||||
|
||||
I aim to ship a fix within 14 days for high-severity issues and within 30 days for everything else. If a fix needs more
|
||||
time I will say so in the private thread.
|
||||
I aim to ship a fix within 14 days for high-severity issues and within 30 days for everything else.
|
||||
If a fix needs more time I will say so in the private thread.
|
||||
|
||||
## Credits
|
||||
|
||||
Everyone who reports a real issue gets listed in the changelog of the release that fixes it, unless they prefer to stay
|
||||
anonymous. No bug bounty, nothing financial — this is a hobby plugin.
|
||||
Everyone who reports a real issue gets listed in the changelog of the release that fixes it, unless
|
||||
they prefer to stay anonymous. No bug bounty, nothing financial — this is a hobby plugin.
|
||||
|
||||
+18
-17
@@ -1,7 +1,7 @@
|
||||
# Support
|
||||
|
||||
HellionChat is a small hobby project maintained by one person. There are a few different paths depending on what you
|
||||
need. Pick the one that matches.
|
||||
HellionChat is a small hobby project maintained by one person. There are a few different paths
|
||||
depending on what you need. Pick the one that matches.
|
||||
|
||||
## Bugs and feature requests
|
||||
|
||||
@@ -10,45 +10,46 @@ Gitea issues, using the templates:
|
||||
- [Bug report](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=bug_report.yml)
|
||||
- [Feature request](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues/new?template=feature_request.yml)
|
||||
|
||||
Please search [existing issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues?type=issue)
|
||||
Please search
|
||||
[existing issues](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues?type=issue)
|
||||
first. Duplicates get closed and pointed at the original.
|
||||
|
||||
## Security
|
||||
|
||||
Do **not** open a public issue for security-relevant findings. Use the private advisory route described in
|
||||
[SECURITY.md](SECURITY.md):
|
||||
Do **not** open a public issue for security-relevant findings. Use the private advisory route
|
||||
described in [SECURITY.md](SECURITY.md):
|
||||
|
||||
- Email `kontakt@hellion-media.de` (preferred for security reports)
|
||||
- Discord DM `@j.j_kazama` for time-sensitive findings
|
||||
|
||||
## Privacy questions
|
||||
|
||||
Specific questions about what HellionChat does or does not store and send are covered in [PRIVACY.md](PRIVACY.md). For
|
||||
follow-ups beyond that document:
|
||||
Specific questions about what HellionChat does or does not store and send are covered in
|
||||
[PRIVACY.md](PRIVACY.md). For follow-ups beyond that document:
|
||||
|
||||
- Email `kontakt@hellion-media.de`
|
||||
|
||||
## Quick questions and casual feedback
|
||||
|
||||
- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and tools):
|
||||
[discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
|
||||
- **Hellion Forge Discord** (community for HellionChat and other Hellion Online Media plugins and
|
||||
tools): [discord.gg/X9V7Kcv5gR](https://discord.gg/X9V7Kcv5gR)
|
||||
- Discord DM: `@j.j_kazama`
|
||||
|
||||
Bug reports still go through the issue tracker so they can be tracked, but a quick "is this a bug or am I holding it
|
||||
wrong" message is fine.
|
||||
Bug reports still go through the issue tracker so they can be tracked, but a quick "is this a bug or
|
||||
am I holding it wrong" message is fine.
|
||||
|
||||
## Upstream Chat 2 issues
|
||||
|
||||
If the issue exists in upstream Chat 2 too, please report it at
|
||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo/issues). That keeps the original maintainers in the loop
|
||||
and helps everyone who uses Chat 2 directly.
|
||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo/issues). That keeps the original
|
||||
maintainers in the loop and helps everyone who uses Chat 2 directly.
|
||||
|
||||
## Response times
|
||||
|
||||
Weekdays during European business hours. On weekends and FFXIV patch days, replies will be slower. A few days of silence
|
||||
on a non-urgent issue is normal. Pinging once after a week is fine.
|
||||
Weekdays during European business hours. On weekends and FFXIV patch days, replies will be slower. A
|
||||
few days of silence on a non-urgent issue is normal. Pinging once after a week is fine.
|
||||
|
||||
---
|
||||
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad Harzburg |
|
||||
[hellion-media.de](https://hellion-media.de)
|
||||
Maintained under **Hellion Forge**, the modding and plugin line of **Hellion Online Media** | Bad
|
||||
Harzburg | [hellion-media.de](https://hellion-media.de)
|
||||
|
||||
+25
-19
@@ -1,13 +1,15 @@
|
||||
# AI Assistance Disclosure
|
||||
|
||||
HellionChat uses AI assistance per the [Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/)
|
||||
at the **Pair** level.
|
||||
HellionChat uses AI assistance per the
|
||||
[Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/) at the **Pair**
|
||||
level.
|
||||
|
||||
A note up front: HellionChat is currently not submitted to the official Dalamud plugin repository and technically has no
|
||||
obligation to disclose this. I would rather be upfront about how it is built.
|
||||
A note up front: HellionChat is currently not submitted to the official Dalamud plugin repository
|
||||
and technically has no obligation to disclose this. I would rather be upfront about how it is built.
|
||||
|
||||
HellionChat is my entry point into game modding and plugin development. I have never written a plugin for a game before.
|
||||
I work alone, so I get help where I need it. That is not something I want to hide.
|
||||
HellionChat is my entry point into game modding and plugin development. I have never written a
|
||||
plugin for a game before. I work alone, so I get help where I need it. That is not something I want
|
||||
to hide.
|
||||
|
||||
## How I Actually Work
|
||||
|
||||
@@ -18,12 +20,13 @@ I plan the architecture, decide what gets built, and own every design decision.
|
||||
- Read the Dalamud log output to verify behaviour
|
||||
- Run security and privacy audits on anything that touches user data
|
||||
|
||||
One of the main reasons I use AI is consistency. I want the HellionChat code to match the style of the upstream Chat 2
|
||||
codebase and stay readable for anyone who opens the repo, not just for me. Claude helps me catch when I am drifting from
|
||||
upstream conventions or writing something that only makes sense in my own head.
|
||||
One of the main reasons I use AI is consistency. I want the HellionChat code to match the style of
|
||||
the upstream Chat 2 codebase and stay readable for anyone who opens the repo, not just for me.
|
||||
Claude helps me catch when I am drifting from upstream conventions or writing something that only
|
||||
makes sense in my own head.
|
||||
|
||||
The balance is shifting toward more hand-written work as I get more comfortable with Dalamud and plugin development in
|
||||
general.
|
||||
The balance is shifting toward more hand-written work as I get more comfortable with Dalamud and
|
||||
plugin development in general.
|
||||
|
||||
## What AI Is Used For
|
||||
|
||||
@@ -34,23 +37,25 @@ general.
|
||||
|
||||
## What AI Is Not Used For
|
||||
|
||||
- **Visual assets.** Logos, icons, banners, and screenshots are human-drawn or taken from the running game.
|
||||
- **Visual assets.** Logos, icons, banners, and screenshots are human-drawn or taken from the
|
||||
running game.
|
||||
- **German translations.** Written by me as a native speaker.
|
||||
|
||||
## What Is Where
|
||||
|
||||
Upstream Chat 2 (by Infi & Anna, EUPL-1.2) is the foundation and was not produced with AI assistance.
|
||||
HellionChat-specific code lives in `HellionChat/Privacy/`, `HellionChat/Export/`,
|
||||
`HellionChat/Resources/HellionStrings*`, `Ui/SettingsTabs/Privacy.cs`, `Ui/FirstRunWizard.cs`, `Ui/HellionStyle.cs`,
|
||||
plus the Migrate3 recovery and plugin layout migration in `MessageStore.cs` and `Plugin.cs`. These were developed with
|
||||
Pair-level assistance as described above.
|
||||
Upstream Chat 2 (by Infi & Anna, EUPL-1.2) is the foundation and was not produced with AI
|
||||
assistance. HellionChat-specific code lives in `HellionChat/Privacy/`, `HellionChat/Export/`,
|
||||
`HellionChat/Resources/HellionStrings*`, `Ui/SettingsTabs/Privacy.cs`, `Ui/FirstRunWizard.cs`,
|
||||
`Ui/HellionStyle.cs`, plus the Migrate3 recovery and plugin layout migration in `MessageStore.cs`
|
||||
and `Plugin.cs`. These were developed with Pair-level assistance as described above.
|
||||
|
||||
## If AI-Assisted Development Is a Dealbreaker for You
|
||||
|
||||
Fair enough. There are solid alternatives:
|
||||
|
||||
- [Chat 2](https://github.com/Infiziert90/ChatTwo), the upstream project HellionChat is built on
|
||||
- [XIV Instant Messenger](https://github.com/NightmareXIV/XIVInstantMessenger), a different approach to FFXIV chat
|
||||
- [XIV Instant Messenger](https://github.com/NightmareXIV/XIVInstantMessenger), a different approach
|
||||
to FFXIV chat
|
||||
|
||||
Both are good projects. Use what fits you best.
|
||||
|
||||
@@ -71,4 +76,5 @@ Both are good projects. Use what fits you best.
|
||||
|
||||
## Contact
|
||||
|
||||
Questions about this disclosure: <https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues>
|
||||
Questions about this disclosure:
|
||||
<https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues>
|
||||
|
||||
+472
-280
@@ -1,39 +1,183 @@
|
||||
# Changelog — Hellion Chat
|
||||
|
||||
All user-facing changes to Hellion Chat. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
version numbers follow [Semantic Versioning](https://semver.org/).
|
||||
All user-facing changes to Hellion Chat. Format follows
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), version numbers follow
|
||||
[Semantic Versioning](https://semver.org/).
|
||||
|
||||
Detailed release notes per version are available directly on the
|
||||
[Gitea Release page](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases) and in the plugin
|
||||
changelog block (`HellionChat/HellionChat.yaml` → `changelog:`). This file summarises releases as an overview and links
|
||||
to the release pages for details.
|
||||
[Gitea Release page](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases) and
|
||||
in the plugin changelog block (`HellionChat/HellionChat.yaml` → `changelog:`). This file summarises
|
||||
releases as an overview and links to the release pages for details.
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.5.1 — FontAtlas Refactor and Hellion Forge Signature (2026-05-17)
|
||||
|
||||
Hybrid FontManager refactor plus an embedded Hellion Forge provenance mark.
|
||||
|
||||
- FontManager handle creation moves into the ctor inside a single `atlas.SuppressAutoRebuild()`
|
||||
block — the font atlas now builds once per plugin load instead of four to five times.
|
||||
- Hybrid property model: `Axis`, `AxisItalic` and `FontAwesome` become init-only handles;
|
||||
`RegularFont` and `ItalicFont` stay mutable because the eight font settings still need to replace
|
||||
them at runtime via the renamed `RebuildDelegateFonts()` path, without a plugin reload.
|
||||
- `FontAwesome` reuses Dalamud's `UiBuilder.IconFontFixedWidthHandle` instead of building its own
|
||||
atlas slot.
|
||||
- `BuildFontsAsync` and `BuildFonts` are removed; the live mutation path is `RebuildDelegateFonts()`
|
||||
now.
|
||||
- Two new self-test steps register with `/xlperf`: FontManager ctor smoke (every handle non-null, no
|
||||
atlas load-exception) and push smoke (`Push()` returns without throwing).
|
||||
- Hellion Forge signature embedded in the plugin DLL: a fox-head ASCII silhouette is prepended to
|
||||
the `/xllog` bootstrap banner on every plugin load, and a full fox banner with "Hellion Forge" set
|
||||
inside the body is available as a folded `TreeNode` in the First-Run Wizard and Settings →
|
||||
Information tab. Drawn by Julia Moon.
|
||||
- Honorific full-gradient port (originally scheduled for this cycle) was dropped: Honorific 3.2
|
||||
exposes no IPC for the rendered gradient frame, and an in-plugin port of the colour palette was
|
||||
declined. The integration stays at the v1.4.7 glow-only shape.
|
||||
|
||||
The 10× HITCH cut targeted from the v1.5.0 cross-plugin baseline did not land — HITCH stays around
|
||||
80 ms because the cost lives in the UiBuilder first-frame render path, not in the atlas build (which
|
||||
this cycle did reduce from 4-5 builds per load to 1). A first-frame render investigation is reserved
|
||||
for a later cycle. No settings changes, no migration. v17 stays.
|
||||
|
||||
[Full release notes on the Gitea release page.](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.5.1)
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.5.0 — DI Foundation and Service Refactor (2026-05-17)
|
||||
|
||||
Major architecture cycle. The plugin bootstrap moves to a generic-host DI container
|
||||
(`Microsoft.Extensions.Hosting` + `IServiceCollection`) modelled on Lightless Sync. Service logging
|
||||
migrates from a static `Plugin.LogProxy` locator to typed `Microsoft.Extensions.Logging.ILogger<T>`
|
||||
via constructor injection, bridged over Dalamud's `IPluginLog` by a custom `DalamudLogger` trio.
|
||||
|
||||
### Under the hood
|
||||
|
||||
- 18 instance-class services migrate to `ILogger<T>` via constructor injection across four slices:
|
||||
data layer (`MessageStore`, `MessageManager`, `AutoTellTabsService`), IPC and integrations
|
||||
(`HonorificService`, `IpcManager`, `TypingIpc`, `ExtraChat`, three `GameFunctions` classes), UI
|
||||
window layer (`ChatLogWindow`, `DbViewer`, `Popout`, three settings tabs), and root (`Commands`,
|
||||
`ThemeRegistry`, `PayloadHandler`).
|
||||
- `Plugin.LogProxy` stays in place for the eight buckets ctor injection cannot reach: static helpers
|
||||
(`EmoteCache`, `AutoTranslate`, `MemoryUtil`, `WrapperUtil`), Dalamud-reflected types
|
||||
(`Configuration`), the `Message` data class, and instance classes that only log from static
|
||||
methods (`FontManager`, one `GameFunctions` site).
|
||||
- Plugin.cs finishes at 1012 lines — virtually identical to the pre-cycle 1013. The new Phase-1 host
|
||||
build and `Plugin.X` bridge wiring trade out exactly the service and window allocations that
|
||||
previously lived in `LoadAsync`.
|
||||
- Cross-plugin baseline confirms no performance penalty against Chat 2: HellionChat first-frame
|
||||
HITCH 77 ms median, Chat 2 74 ms median. Lightless and XIVInstantMessenger sit around 7 ms by
|
||||
deferring their font-atlas build past `Finished loading` — that pattern is the v1.5.1 follow-up
|
||||
item.
|
||||
|
||||
### User-visible
|
||||
|
||||
- Slash-command insert fix: pasting a slash command into the chat input (Friend List "/tell" action,
|
||||
plugin-driven inserts from Artisan, AllaganTools etc.) now replaces the existing input instead of
|
||||
concatenating onto whatever the user was typing. Cherry-picked from ChatTwo upstream `ee7768ac`
|
||||
with namespace adaptation.
|
||||
|
||||
Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)
|
||||
|
||||
Eleventh and final sub-patch of the v1.4.x Polish-Sweep series. Symbol picker for the chat input, a
|
||||
tell-history reload fix for users with many active partners, and a closing cleanup sweep before
|
||||
v1.5.0 picks up the DI-container adoption.
|
||||
|
||||
- Symbol picker for the chat input: smile-icon button left of the channel indicator opens a popup
|
||||
with two tabs — 161 FFXIV PUA glyphs (Dalamud's SeIconChar enum) and 97 server-verified BMP
|
||||
symbols round-tripped through `/echo` and `/say` in a four-round probe. Cursor-aware splice,
|
||||
multi-insert keeps the popup open, recent-used strip floats the last sixteen picks across both
|
||||
tabs. Toggle in Settings → Chat → Message behaviour, default on.
|
||||
- Pinned auto-tell tabs reload their full history again. PreloadHistory had a hidden 500-row scan
|
||||
cap that overrode the user-configurable `AutoTellTabsHistoryPreload` setting whenever you chatted
|
||||
with many partners; less-frequent pinned partners lost their backlog. The cap is removed.
|
||||
- Slash-command teardown cleanup: `/hellion`, `/hellionView`, `/hellionDebugger` (and
|
||||
`#if DEBUG /hellionSeString`) wrappers are now cached as private fields so plugin teardown
|
||||
detaches the live registration instead of re-Register'ing with identical args (latent maintenance
|
||||
hazard from v1.4.9).
|
||||
- v1.4.x Polish-Sweep wraps up here. The ImGuiListClipper render refactor that was on the v1.4.10
|
||||
reserve list got dropped after cross-platform smoke showed the scroll rubber-band is a Wine/Linux
|
||||
render-pipeline quirk, not universal — Windows users never saw it. It will get its own
|
||||
platform-targeted spike in a later patch. Next major cycle is v1.5.0 with the DI-container
|
||||
adoption (Microsoft.Extensions.Hosting + ILogger<T>) modelled on Lightless.
|
||||
- Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.4.9 — Plugin-Load Render Polish (2026-05-15)
|
||||
|
||||
Tenth sub-patch of the v1.4.x polish-sweep series. First-frame render cost drops from ~127 ms median
|
||||
down to ~76 ms median — comfortably under Dalamud's 100 ms HITCH warning threshold. The remaining
|
||||
~13 ms gap to ChatTwo upstream (~63 ms median) is the cost of HellionChat-only features (sidebar tab
|
||||
view, custom status bar, Honorific integration).
|
||||
|
||||
- First-frame defer: six non-essential rendering sections inside `ChatLogWindow` skip their first
|
||||
Draw and run one frame later. Covered sections are the bottom status bar, channel-name SeString
|
||||
chunks, window bounds check, v0.6.1 hint banner, autocomplete and input-preview calculation. At 60
|
||||
fps the user sees those sections ~17 ms after plugin reload — invisible inside the ~2.5 s
|
||||
font-atlas build window every reload runs through anyway. Frame 1 stays well under 100 ms too (~40
|
||||
ms), so no secondary HITCH warning appears.
|
||||
- Slash-command centralisation: `/hellion`, `/hellionView`, `/hellionSeString` and
|
||||
`/hellionDebugger` are now registered during `LoadAsync` instead of inside the corresponding
|
||||
window constructors. The commands work before their target window is opened the first time, and
|
||||
Dalamud's plugin-manager configuration / open buttons (`UiBuilder.OpenConfigUi` / `OpenMainUi`)
|
||||
hang on the same path.
|
||||
- Plugin-load profiling logs stay on: `MessageStore.Connect`, `MessageStore.Migrate`,
|
||||
`FilterAllTabs` and the auto-translate warm-up timing log are now Information level rather than
|
||||
Debug. They serve as a tripwire so a future regression past 100 ms shows up directly in `/xllog`
|
||||
without re-enabling Debug.
|
||||
- ChatTwo IPC compatibility layer: HellionChat now mirrors ChatTwo's full IPC surface
|
||||
(`GetChatInputState`, `ChatInputStateChanged`, `Register`, `Unregister`, `Available`, `Invoke`)
|
||||
under the `ChatTwo.*` namespace in addition to our existing `HellionChat.*` provider gates.
|
||||
Third-party integrations that historically only subscribe to ChatTwo's IPC — for example Artisan's
|
||||
and AllaganTools' context-menu hooks — keep working without requiring a code change on their side.
|
||||
Conflict detection prevents ChatTwo from loading in parallel with HellionChat, so there is no
|
||||
slot-collision risk at runtime.
|
||||
- Migration v17 stays (no schema bump).
|
||||
- Internal: hypothesis-triage during the R2 cycle falsified three of the four candidate root causes
|
||||
(font-atlas sync, theme-apply ABGR-cache init, multiple-window render). Actual cause is `DrawList`
|
||||
setup cost distributed across ~10 ImGui sections inside ChatLogWindow (5-20 ms each). The six
|
||||
selective defers above are the pragmatic fix — a clean structural rewrite would belong in the
|
||||
v1.5.x DI-container cycle.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
## Hellion Chat 1.4.8 — Hook-Layer and Polish Quick-Wins (2026-05-14)
|
||||
|
||||
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search, ad-block foundation
|
||||
investigation) plus three polish quick-wins.
|
||||
Ninth sub-patch of the v1.4.x polish-sweep series. Hook-layer cluster (FTS5 full-text search,
|
||||
ad-block foundation investigation) plus three polish quick-wins.
|
||||
|
||||
- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously on first run after
|
||||
the update with a progress toast (UI stays responsive, the toggle is disabled until the build completes). The local
|
||||
page-filter remains the default mode. Multi-word queries match as exact phrases; power users can opt into raw FTS5
|
||||
`MATCH` syntax by wrapping their own double-quotes around the term.
|
||||
- Custom theme files now auto-reload when edited while the theme is active. Save the JSON in your editor and the live
|
||||
render picks up the change within a second — no need to re-click the theme in the picker. Disk-stat is throttled to
|
||||
1 Hz so per-frame cost stays free.
|
||||
- DbViewer full-text search: optional FTS5 index across the full chat history. Built asynchronously
|
||||
on first run after the update with a progress toast (UI stays responsive, the toggle is disabled
|
||||
until the build completes). The local page-filter remains the default mode. Multi-word queries
|
||||
match as exact phrases; power users can opt into raw FTS5 `MATCH` syntax by wrapping their own
|
||||
double-quotes around the term.
|
||||
- Custom theme files now auto-reload when edited while the theme is active. Save the JSON in your
|
||||
editor and the live render picks up the change within a second — no need to re-click the theme in
|
||||
the picker. Disk-stat is throttled to 1 Hz so per-frame cost stays free.
|
||||
- Retention sweep no longer blocks the framework thread. `Framework.Run(...).Wait()` is replaced by
|
||||
`Framework.RunOnTick(...)`, which removes the ~194 ms hitch the sweep used to add per run.
|
||||
- Status bar height is derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the bar renders
|
||||
correctly at Windows display scaling above 100 %. Linux/Wayland default of 100 % is unaffected.
|
||||
- Receive-suppressed-tells routing was investigated this cycle and **postponed to v1.5.x**. When other plugins suppress
|
||||
tells via `CheckMessageHandled`, FFXIV's chat pipeline skips the `RaptureLogModule.AddMsgSourceEntry` path, which means
|
||||
HellionChat's `ContentIdResolverHook` does not fire and tell-partner identification breaks for AutoTellTab routing.
|
||||
The proper fix sits next to the planned ad-block hook layer (`RaptureLogModule.ShowMiniTalkPlayer` and friends) where
|
||||
the same patch surface comes up anyway.
|
||||
- Internal: storage form of `messages.Id` clarified (declared BLOB but Microsoft.Data.Sqlite stores Guid parameters as
|
||||
TEXT). FTS bulk insert and `LoadByGuids` join now match the TEXT storage form on both sides. Migration v17 stays
|
||||
(no schema bump).
|
||||
- Status bar height is derived from `GetTextLineHeightWithSpacing()` plus a DPI-aware spacer so the
|
||||
bar renders correctly at Windows display scaling above 100 %. Linux/Wayland default of 100 % is
|
||||
unaffected.
|
||||
- Receive-suppressed-tells routing was investigated this cycle and **postponed to v1.5.x**. When
|
||||
other plugins suppress tells via `CheckMessageHandled`, FFXIV's chat pipeline skips the
|
||||
`RaptureLogModule.AddMsgSourceEntry` path, which means HellionChat's `ContentIdResolverHook` does
|
||||
not fire and tell-partner identification breaks for AutoTellTab routing. The proper fix sits next
|
||||
to the planned ad-block hook layer (`RaptureLogModule.ShowMiniTalkPlayer` and friends) where the
|
||||
same patch surface comes up anyway.
|
||||
- Internal: storage form of `messages.Id` clarified (declared BLOB but Microsoft.Data.Sqlite stores
|
||||
Guid parameters as TEXT). FTS bulk insert and `LoadByGuids` join now match the TEXT storage form
|
||||
on both sides. Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
@@ -41,38 +185,43 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.7 — Backlog Cleanup and Mid-Features (2026-05-13)
|
||||
|
||||
Eighth sub-patch of the v1.4.x polish-sweep series. First user-visible feature bundle since v1.4.5 — pinned tell tabs
|
||||
that survive relog, opt-in Honorific glow rendering, a configurable sidebar, plus a Settings-Save channel-preservation
|
||||
fix surfaced during smoke testing.
|
||||
Eighth sub-patch of the v1.4.x polish-sweep series. First user-visible feature bundle since v1.4.5 —
|
||||
pinned tell tabs that survive relog, opt-in Honorific glow rendering, a configurable sidebar, plus a
|
||||
Settings-Save channel-preservation fix surfaced during smoke testing.
|
||||
|
||||
- TempTell Pin: right-click a TempTell tab in the sidebar and choose "Pin Tab" / "Tab anpinnen". Pinned tabs survive
|
||||
plugin reload and character logout, keep their conversation history (loaded on demand from the message store on
|
||||
rehydrate), and stay bound to the same `/tell` partner. Hard cap of 5 pinned tabs in a pool separate from the 15-tab
|
||||
auto-tell pool — total ceiling is 20 tabs. The sidebar groups pinned tabs into their own section with a divider header
|
||||
- Honorific glow outlines now render via an 8-direction DrawList pre-pass when the title carries a Glow colour. Opt-in
|
||||
via **Settings → Integrations → Render glow outlines (Honorific)** (default off). Honorific's gradient surface
|
||||
(`Color3`, `GradientColourSet`, `GradientAnimationStyle`) is parsed and stashed for a later cycle but renders as the
|
||||
primary colour until then — the v1.4.7 DTO already mirrors all four extra fields so the JSON roundtrip doesn't
|
||||
silent-drop them
|
||||
- Sidebar width configurable in **Theme & Layout** (44–160 px, default 44 stays icon-only). The icon button stretches
|
||||
with the configured width so a widened sidebar looks intentional, not a 36 px icon floating in empty space
|
||||
- `Configuration.UpdateFrom` now preserves the runtime `CurrentChannel` across the persistent-tab merge alongside
|
||||
`Messages` and `LastSendUnread`. `TabSwitched` deep-clones the seeded channel from the previous tab instead of sharing
|
||||
the same `UsedChannel` instance. Together these fix a regression where Settings-Save on a Party or Linkshell tab
|
||||
popped the chat input back to `/tell <pinned-partner>` on the next interaction
|
||||
- `Util/ImGuiUtil.cs` `DrawArrows` IconButton id uses `(id + 1).ToString()` with explicit parentheses instead of the
|
||||
operator-precedence quirk `id + 1.ToString()` (which resolved to `id.ToString() + "1"`). Single live caller is
|
||||
`Ui/DbViewer.cs:227` page-navigation
|
||||
- Internal: `IPluginLogProxy` indirection over Dalamud's `IPluginLog` routes all ~91 `Plugin.Log` call sites through a
|
||||
testable proxy. `MessageStore.Migrate0` can now run in xUnit without loading `Dalamud.dll`, closing the gap F12.1 left
|
||||
in v1.4.6. Production wrapper `DalamudPluginLogProxy` and Build-Suite `FakePluginLogProxy` mirror the full
|
||||
`IPluginLog` surface (`Verbose`/`Debug`/`Information`/`Info`/`Warning`/`Error`/`Fatal`) with single-string,
|
||||
- TempTell Pin: right-click a TempTell tab in the sidebar and choose "Pin Tab" / "Tab anpinnen".
|
||||
Pinned tabs survive plugin reload and character logout, keep their conversation history (loaded on
|
||||
demand from the message store on rehydrate), and stay bound to the same `/tell` partner. Hard cap
|
||||
of 5 pinned tabs in a pool separate from the 15-tab auto-tell pool — total ceiling is 20 tabs. The
|
||||
sidebar groups pinned tabs into their own section with a divider header
|
||||
- Honorific glow outlines now render via an 8-direction DrawList pre-pass when the title carries a
|
||||
Glow colour. Opt-in via **Settings → Integrations → Render glow outlines (Honorific)** (default
|
||||
off). Honorific's gradient surface (`Color3`, `GradientColourSet`, `GradientAnimationStyle`) is
|
||||
parsed and stashed for a later cycle but renders as the primary colour until then — the v1.4.7 DTO
|
||||
already mirrors all four extra fields so the JSON roundtrip doesn't silent-drop them
|
||||
- Sidebar width configurable in **Theme & Layout** (44–160 px, default 44 stays icon-only). The icon
|
||||
button stretches with the configured width so a widened sidebar looks intentional, not a 36 px
|
||||
icon floating in empty space
|
||||
- `Configuration.UpdateFrom` now preserves the runtime `CurrentChannel` across the persistent-tab
|
||||
merge alongside `Messages` and `LastSendUnread`. `TabSwitched` deep-clones the seeded channel from
|
||||
the previous tab instead of sharing the same `UsedChannel` instance. Together these fix a
|
||||
regression where Settings-Save on a Party or Linkshell tab popped the chat input back to
|
||||
`/tell <pinned-partner>` on the next interaction
|
||||
- `Util/ImGuiUtil.cs` `DrawArrows` IconButton id uses `(id + 1).ToString()` with explicit
|
||||
parentheses instead of the operator-precedence quirk `id + 1.ToString()` (which resolved to
|
||||
`id.ToString() + "1"`). Single live caller is `Ui/DbViewer.cs:227` page-navigation
|
||||
- Internal: `IPluginLogProxy` indirection over Dalamud's `IPluginLog` routes all ~91 `Plugin.Log`
|
||||
call sites through a testable proxy. `MessageStore.Migrate0` can now run in xUnit without loading
|
||||
`Dalamud.dll`, closing the gap F12.1 left in v1.4.6. Production wrapper `DalamudPluginLogProxy`
|
||||
and Build-Suite `FakePluginLogProxy` mirror the full `IPluginLog` surface
|
||||
(`Verbose`/`Debug`/`Information`/`Info`/`Warning`/`Error`/`Fatal`) with single-string,
|
||||
`Exception+string`, and `params object[]` overloads
|
||||
- Internal: TempTab counter switched from an `Interlocked` cached field to a derived `Tabs.Count(predicate)`. Pin-state
|
||||
transitions (TryPin / Unpin / Promote) are cold-path and don't need lock-free reads; counter mutation surface dropped
|
||||
from 5 to 0 sites. Build-Suite floor 688 → 710 (+22)
|
||||
- Schema bump v16 → v17 is additive: new `Tab.IsPinned` bool, default false. Existing v16 configs load cleanly and get
|
||||
their `Version` stamp bumped after the gate check
|
||||
- Internal: TempTab counter switched from an `Interlocked` cached field to a derived
|
||||
`Tabs.Count(predicate)`. Pin-state transitions (TryPin / Unpin / Promote) are cold-path and don't
|
||||
need lock-free reads; counter mutation surface dropped from 5 to 0 sites. Build-Suite floor 688 →
|
||||
710 (+22)
|
||||
- Schema bump v16 → v17 is additive: new `Tab.IsPinned` bool, default false. Existing v16 configs
|
||||
load cleanly and get their `Version` stamp bumped after the gate check
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
@@ -80,45 +229,51 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.6 — Code Hygiene and Refactor (2026-05-12)
|
||||
|
||||
Maintenance patch. No user-visible behaviour changes; tightens the development feedback loop, fixes two
|
||||
upstream-inherited bugs from ChatTwo `f35b7d3`, and prepares the code for the v1.4.7 backlog cleanup.
|
||||
Maintenance patch. No user-visible behaviour changes; tightens the development feedback loop, fixes
|
||||
two upstream-inherited bugs from ChatTwo `f35b7d3`, and prepares the code for the v1.4.7 backlog
|
||||
cleanup.
|
||||
|
||||
- `scripts/preflight.sh` gains Block E (`dotnet csharpier check`) and Block F (`markdownlint-cli2`) so reflow drift and
|
||||
markdown violations are caught at the pre-push gate. `.markdownlint.json` adds `MD024 siblings_only` and disables
|
||||
`MD036` so the bilingual forge-post bold-emphasis headings pass linting; the `.claude/` directory is excluded from the
|
||||
scan
|
||||
- `FontManager.AddFontWithFallback` catch-filter now covers `InvalidOperationException` and `ArgumentException` on top
|
||||
of the existing IO triad. The warning log carries the exception type name, so the diagnostic path knows which class of
|
||||
atlas-toolkit throw triggered the NotoSansCjkRegular fallback
|
||||
- `BrandingLinks` (5 URLs) and `Integrations/IntegrationLinks` (2 URLs) validate themselves on first module load via
|
||||
`[ModuleInitializer]` + a shared `UrlValidation.ValidateAll` helper. A malformed URL now throws
|
||||
`InvalidOperationException` at plugin load with the source class and the broken URL in the message
|
||||
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the native `Utf8String` when the
|
||||
linkshell check rejects the channel. The validity check is now wrapped around the `ChangeChatChannel` call instead of
|
||||
short-circuiting before `Dtor`. `ValidAnyLinkshell` is renamed to `IsChannelOrExistingLinkshell` and the
|
||||
`ChatLogWindow` call-site follows the rename
|
||||
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Tab.Clone` now deep-clones `UsedChannel` and `TellTarget`. The old
|
||||
`CurrentChannel = CurrentChannel` was a reference copy, so PopOut and Temp tabs mutated each other's channel state
|
||||
(incl. tell target). `TellTarget.From(t)` static factory is replaced with an instance `Clone()`; `UsedChannel.Clone()`
|
||||
is new and runs deep-clone on both TellTarget references
|
||||
- `ChatLogWindow` active-tab underline pill now scales with `ImGuiHelpers.GlobalScale` and rounds its DrawList
|
||||
coordinates to physical pixels via `MathF.Round`, so the 2 px line stays crisp on 125 % and 150 % DPI setups instead
|
||||
of bleeding into a sub-pixel blur
|
||||
- `ImGuiUtil.IconButton` width parameter no longer subtracts HUD-scaled `CellPadding.X * 2` from the raw `int` width.
|
||||
`ImGui.Button` handles its own frame padding internally, so the measured `buttonWidth` now passes through verbatim
|
||||
(inspired-by upstream `f35b7d3`, but our two call-sites need the parameter, so the param itself stays)
|
||||
- Internal: `HellionStyle` ChildBgAlpha threshold logic extracted to `HellionStyleHelpers.ResolveChildBgAlpha` with a
|
||||
build-suite mirror test that pins the 0.999f cutoff. `Plugin.SaveConfig` clones only the temp-tab subset in the
|
||||
pre-serialization snapshot instead of the full tab list. `SettingsOverview` caches `ImGui.GetWindowDrawList()` once
|
||||
per frame and passes the pointer down to `DrawCard`
|
||||
- Internal: `Dalamud.Utility.Util` static surface (`IsWine`, `OpenLink`) routed through a new `IPlatformUtil`
|
||||
indirection. `MessageStore`'s `IsWine` probe is now reachable from the xUnit AppDomain via a `FakePlatformUtil`
|
||||
fixture (full isolated MessageStore construction still pending — `Plugin.Log.Information` in `Migrate0` is a separate
|
||||
Dalamud-static surface, slated for v1.4.7)
|
||||
- Built-in themes: Crystal Nocturne (royal sapphire and electric magenta over obsidian, by CRYSTALLITE) replaces Moonlit
|
||||
Bloom in the built-in roster. Users who had Moonlit Bloom selected fall back to the default Hellion Arctic on the
|
||||
first plugin load; an existing custom JSON copy of Moonlit Bloom under `pluginConfigs/HellionChat/themes/` keeps
|
||||
working unchanged
|
||||
- `scripts/preflight.sh` gains Block E (`dotnet csharpier check`) and Block F (`markdownlint-cli2`)
|
||||
so reflow drift and markdown violations are caught at the pre-push gate. `.markdownlint.json` adds
|
||||
`MD024 siblings_only` and disables `MD036` so the bilingual forge-post bold-emphasis headings pass
|
||||
linting; the `.claude/` directory is excluded from the scan
|
||||
- `FontManager.AddFontWithFallback` catch-filter now covers `InvalidOperationException` and
|
||||
`ArgumentException` on top of the existing IO triad. The warning log carries the exception type
|
||||
name, so the diagnostic path knows which class of atlas-toolkit throw triggered the
|
||||
NotoSansCjkRegular fallback
|
||||
- `BrandingLinks` (5 URLs) and `Integrations/IntegrationLinks` (2 URLs) validate themselves on first
|
||||
module load via `[ModuleInitializer]` + a shared `UrlValidation.ValidateAll` helper. A malformed
|
||||
URL now throws `InvalidOperationException` at plugin load with the source class and the broken URL
|
||||
in the message
|
||||
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Chat.SetChannel` no longer leaks the native
|
||||
`Utf8String` when the linkshell check rejects the channel. The validity check is now wrapped
|
||||
around the `ChangeChatChannel` call instead of short-circuiting before `Dtor`. `ValidAnyLinkshell`
|
||||
is renamed to `IsChannelOrExistingLinkshell` and the `ChatLogWindow` call-site follows the rename
|
||||
- Cherry-picked from ChatTwo upstream `f35b7d3`: `Tab.Clone` now deep-clones `UsedChannel` and
|
||||
`TellTarget`. The old `CurrentChannel = CurrentChannel` was a reference copy, so PopOut and Temp
|
||||
tabs mutated each other's channel state (incl. tell target). `TellTarget.From(t)` static factory
|
||||
is replaced with an instance `Clone()`; `UsedChannel.Clone()` is new and runs deep-clone on both
|
||||
TellTarget references
|
||||
- `ChatLogWindow` active-tab underline pill now scales with `ImGuiHelpers.GlobalScale` and rounds
|
||||
its DrawList coordinates to physical pixels via `MathF.Round`, so the 2 px line stays crisp on 125
|
||||
% and 150 % DPI setups instead of bleeding into a sub-pixel blur
|
||||
- `ImGuiUtil.IconButton` width parameter no longer subtracts HUD-scaled `CellPadding.X * 2` from the
|
||||
raw `int` width. `ImGui.Button` handles its own frame padding internally, so the measured
|
||||
`buttonWidth` now passes through verbatim (inspired-by upstream `f35b7d3`, but our two call-sites
|
||||
need the parameter, so the param itself stays)
|
||||
- Internal: `HellionStyle` ChildBgAlpha threshold logic extracted to
|
||||
`HellionStyleHelpers.ResolveChildBgAlpha` with a build-suite mirror test that pins the 0.999f
|
||||
cutoff. `Plugin.SaveConfig` clones only the temp-tab subset in the pre-serialization snapshot
|
||||
instead of the full tab list. `SettingsOverview` caches `ImGui.GetWindowDrawList()` once per frame
|
||||
and passes the pointer down to `DrawCard`
|
||||
- Internal: `Dalamud.Utility.Util` static surface (`IsWine`, `OpenLink`) routed through a new
|
||||
`IPlatformUtil` indirection. `MessageStore`'s `IsWine` probe is now reachable from the xUnit
|
||||
AppDomain via a `FakePlatformUtil` fixture (full isolated MessageStore construction still pending
|
||||
— `Plugin.Log.Information` in `Migrate0` is a separate Dalamud-static surface, slated for v1.4.7)
|
||||
- Built-in themes: Crystal Nocturne (royal sapphire and electric magenta over obsidian, by
|
||||
CRYSTALLITE) replaces Moonlit Bloom in the built-in roster. Users who had Moonlit Bloom selected
|
||||
fall back to the default Hellion Arctic on the first plugin load; an existing custom JSON copy of
|
||||
Moonlit Bloom under `pluginConfigs/HellionChat/themes/` keeps working unchanged
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -128,26 +283,31 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.5 — UX and Robustness (2026-05-12)
|
||||
|
||||
Sixth sub-patch of the v1.4.x polish-sweep series. User-visible robustness fixes plus two doc/test polish items from the
|
||||
audit backlog. No schema bump, no migration.
|
||||
Sixth sub-patch of the v1.4.x polish-sweep series. User-visible robustness fixes plus two doc/test
|
||||
polish items from the audit backlog. No schema bump, no migration.
|
||||
|
||||
- `ChatLogWindow.Draw` now surfaces a one-shot warning notification when the draw path throws. The stack trace still
|
||||
goes to `/xllog` via `Plugin.Log.Error`; the notification is suppressed for the rest of the plugin session so a
|
||||
recurring failure can't spam the notification stack frame-by-frame. Pattern-match to the existing `Plugin.cs:505-516`
|
||||
migration-blocker notification
|
||||
- `FirstRunWizard` splits accept from close. `OnClose` no longer silently sets `FirstRunCompleted`, so closing the X
|
||||
leaves the wizard pending and it reopens on the next plugin load. A new footer "Later — keep defaults" button is the
|
||||
explicit path to dismiss without picking a profile. Bilingual strings (EN + DE) plus a tooltip
|
||||
- `InputHistoryService.Reset` is wired into `Plugin.DisposeAsync` alongside the existing pure-memory cleanups. Static
|
||||
state used to survive a plugin reload — the next load now starts with an empty history
|
||||
- `FontManager.GetHellionFontBytes` becomes `TryGetHellionFontBytes` with a nullable return. On miss (broken csproj,
|
||||
hand-rolled dev build) the caller falls back to the system-font path that `UseHellionFont=false` already uses, plus a
|
||||
`Plugin.Log.Warning`. The whole UiBuilder no longer throws if the embedded font resource is absent
|
||||
- `Plugin.cs:167-168` gets a 4-line reasoning comment around the session-only `RemoveAll(IsTempTab)`: tells are usually
|
||||
privacy-filtered, resurrecting an empty crashed-session tab would trigger DB reconstruction on the next load.
|
||||
`TempTabCounter.InitFromList` mirrors the post-strip semantic in the Build-Suite with a pinning test
|
||||
- `StatusBar.cs` drops the version slot when the chat window's content width minus the version text is below 200 px. The
|
||||
right-aligned version used to clip into the four left-side slots in narrow windows
|
||||
- `ChatLogWindow.Draw` now surfaces a one-shot warning notification when the draw path throws. The
|
||||
stack trace still goes to `/xllog` via `Plugin.Log.Error`; the notification is suppressed for the
|
||||
rest of the plugin session so a recurring failure can't spam the notification stack
|
||||
frame-by-frame. Pattern-match to the existing `Plugin.cs:505-516` migration-blocker notification
|
||||
- `FirstRunWizard` splits accept from close. `OnClose` no longer silently sets `FirstRunCompleted`,
|
||||
so closing the X leaves the wizard pending and it reopens on the next plugin load. A new footer
|
||||
"Later — keep defaults" button is the explicit path to dismiss without picking a profile.
|
||||
Bilingual strings (EN + DE) plus a tooltip
|
||||
- `InputHistoryService.Reset` is wired into `Plugin.DisposeAsync` alongside the existing pure-memory
|
||||
cleanups. Static state used to survive a plugin reload — the next load now starts with an empty
|
||||
history
|
||||
- `FontManager.GetHellionFontBytes` becomes `TryGetHellionFontBytes` with a nullable return. On miss
|
||||
(broken csproj, hand-rolled dev build) the caller falls back to the system-font path that
|
||||
`UseHellionFont=false` already uses, plus a `Plugin.Log.Warning`. The whole UiBuilder no longer
|
||||
throws if the embedded font resource is absent
|
||||
- `Plugin.cs:167-168` gets a 4-line reasoning comment around the session-only
|
||||
`RemoveAll(IsTempTab)`: tells are usually privacy-filtered, resurrecting an empty crashed-session
|
||||
tab would trigger DB reconstruction on the next load. `TempTabCounter.InitFromList` mirrors the
|
||||
post-strip semantic in the Build-Suite with a pinning test
|
||||
- `StatusBar.cs` drops the version slot when the chat window's content width minus the version text
|
||||
is below 200 px. The right-aligned version used to clip into the four left-side slots in narrow
|
||||
windows
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -157,29 +317,32 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.4 — Threading and IPC Safety Polish (2026-05-12)
|
||||
|
||||
Fifth sub-patch of the v1.4.x polish-sweep series. Threading assumptions are documented per-method, a hot-path lock
|
||||
falls away in `AutoTellTabsService`, IPC-cleanup failures become visible, and the privacy filter now speaks up when an
|
||||
unknown ChatType shows up.
|
||||
Fifth sub-patch of the v1.4.x polish-sweep series. Threading assumptions are documented per-method,
|
||||
a hot-path lock falls away in `AutoTellTabsService`, IPC-cleanup failures become visible, and the
|
||||
privacy filter now speaks up when an unknown ChatType shows up.
|
||||
|
||||
- `AutoTellTabsService.ActiveTempTabCount` switches from a lock-protected LINQ `Count` to an `Interlocked` counter kept
|
||||
in sync with `Config.Tabs` from inside the existing mutation paths. `Initialize()` seeds the counter from the
|
||||
persisted Tabs list, and `SaveConfig`'s snapshot-restore path calls a new `ResyncTempTabCounter()` so the mid-step
|
||||
`RemoveAll` doesn't leave the counter drifting. Pure-helper test mirror lives in the Build-Suite repo
|
||||
- `HonorificService` per-method threading banners replace the block comment at the bottom of the file. Each IPC callback
|
||||
(`TryInitialPull`, `OnTitleChanged`, `OnReady`, `OnDisposing`, `TryUnsubscribe`) and the `CurrentTitle` field carry a
|
||||
one-line `// Thread:` annotation so the framework-thread invariant is visible at the call site
|
||||
- `TryUnsubscribe` log-level upgraded from `Debug` to `Warning`. A silent unsubscribe failure leaks a live subscription
|
||||
across plugin reloads, which is exactly the kind of issue that should not be at Debug
|
||||
- `AutoTranslate.PreloadCache` thread now has `IsBackground = true` and a thread name. Without `IsBackground` the warmup
|
||||
blocks plugin unload (typically 100-300 ms). Pattern-match to `MessageManager` (F6.1) and `Plugin.RetentionSweep`
|
||||
(F9.3), both since v1.4.0
|
||||
- `Configuration.IsAllowedForStorage` adds a one-line `Plugin.Log.Warning` for the first occurrence of any ChatType that
|
||||
isn't in `PrivacyPersistChannels`. Dedup via a `NonSerialized` `HashSet<ChatType>`, so the warning fires once per
|
||||
runtime — not once per frame, not once per install. Failsafe routing through `PrivacyPersistUnknownChannels` is
|
||||
unchanged
|
||||
- `PrivacyPersistUnknownChannels` field default flipped from `false` to `true` for new installs via a constant in
|
||||
`PrivacyDefaults`. Existing configs keep their explicit choice — the deserializer overrides the initializer. No schema
|
||||
bump, no migration, no first-run banner
|
||||
- `AutoTellTabsService.ActiveTempTabCount` switches from a lock-protected LINQ `Count` to an
|
||||
`Interlocked` counter kept in sync with `Config.Tabs` from inside the existing mutation paths.
|
||||
`Initialize()` seeds the counter from the persisted Tabs list, and `SaveConfig`'s snapshot-restore
|
||||
path calls a new `ResyncTempTabCounter()` so the mid-step `RemoveAll` doesn't leave the counter
|
||||
drifting. Pure-helper test mirror lives in the Build-Suite repo
|
||||
- `HonorificService` per-method threading banners replace the block comment at the bottom of the
|
||||
file. Each IPC callback (`TryInitialPull`, `OnTitleChanged`, `OnReady`, `OnDisposing`,
|
||||
`TryUnsubscribe`) and the `CurrentTitle` field carry a one-line `// Thread:` annotation so the
|
||||
framework-thread invariant is visible at the call site
|
||||
- `TryUnsubscribe` log-level upgraded from `Debug` to `Warning`. A silent unsubscribe failure leaks
|
||||
a live subscription across plugin reloads, which is exactly the kind of issue that should not be
|
||||
at Debug
|
||||
- `AutoTranslate.PreloadCache` thread now has `IsBackground = true` and a thread name. Without
|
||||
`IsBackground` the warmup blocks plugin unload (typically 100-300 ms). Pattern-match to
|
||||
`MessageManager` (F6.1) and `Plugin.RetentionSweep` (F9.3), both since v1.4.0
|
||||
- `Configuration.IsAllowedForStorage` adds a one-line `Plugin.Log.Warning` for the first occurrence
|
||||
of any ChatType that isn't in `PrivacyPersistChannels`. Dedup via a `NonSerialized`
|
||||
`HashSet<ChatType>`, so the warning fires once per runtime — not once per frame, not once per
|
||||
install. Failsafe routing through `PrivacyPersistUnknownChannels` is unchanged
|
||||
- `PrivacyPersistUnknownChannels` field default flipped from `false` to `true` for new installs via
|
||||
a constant in `PrivacyDefaults`. Existing configs keep their explicit choice — the deserializer
|
||||
overrides the initializer. No schema bump, no migration, no first-run banner
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -189,22 +352,26 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.3 — Plugin-Load Async-Init + Repo-Cutover (2026-05-08)
|
||||
|
||||
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin` API. The constructor now does only the bootstrap-essentials
|
||||
(config load, language init, conflict detection); migrations, service allocations, window construction and hook
|
||||
subscription move to `LoadAsync`. Dalamud can keep its UI responsive while the heavy work runs.
|
||||
Plugin lifecycle migrated to Dalamud's `IAsyncDalamudPlugin` API. The constructor now does only the
|
||||
bootstrap-essentials (config load, language init, conflict detection); migrations, service
|
||||
allocations, window construction and hook subscription move to `LoadAsync`. Dalamud can keep its UI
|
||||
responsive while the heavy work runs.
|
||||
|
||||
- `IAsyncDalamudPlugin` two-phase load with per-line `CaptureFailure` in `DisposeAsync` (mirrors LightlessSync's
|
||||
pattern); idempotency guard protects against reload races
|
||||
- Schema-gate replaces the v9 → v16 migration chain. Configs on schema v16+ load directly; older configs trigger an
|
||||
"install v1.4.2 first" error so the historic migration path stays intact
|
||||
- `AutoTranslate.PreloadCache` moved off the load path. First use may have a sub-second hitch instead of every-load; the
|
||||
upstream chose differently, we accept first-use latency
|
||||
- `FontManager.BuildFonts` is called sync at the start of `LoadAsync`; Dalamud rebuilds the font atlas on its own
|
||||
pipeline so the custom Hellion-Exo2 font appears with a brief font-pop after load (matches ChatTwo's behaviour)
|
||||
- Custom-repo URL moved to `gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat`. GitHub repo stays as a frozen
|
||||
v1.4.2 snapshot; new releases ship from Gitea. Existing testers need to update the custom-repo URL once
|
||||
- Plugin-load time in this release sits at ~3.7 s median (5 reloads), comparable to v1.4.2. Async migration is
|
||||
foundational for v1.4.4 Lazy-Init optimisations rather than an immediate user-perceived win
|
||||
- `IAsyncDalamudPlugin` two-phase load with per-line `CaptureFailure` in `DisposeAsync` (mirrors
|
||||
LightlessSync's pattern); idempotency guard protects against reload races
|
||||
- Schema-gate replaces the v9 → v16 migration chain. Configs on schema v16+ load directly; older
|
||||
configs trigger an "install v1.4.2 first" error so the historic migration path stays intact
|
||||
- `AutoTranslate.PreloadCache` moved off the load path. First use may have a sub-second hitch
|
||||
instead of every-load; the upstream chose differently, we accept first-use latency
|
||||
- `FontManager.BuildFonts` is called sync at the start of `LoadAsync`; Dalamud rebuilds the font
|
||||
atlas on its own pipeline so the custom Hellion-Exo2 font appears with a brief font-pop after load
|
||||
(matches ChatTwo's behaviour)
|
||||
- Custom-repo URL moved to `gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat`. GitHub repo
|
||||
stays as a frozen v1.4.2 snapshot; new releases ship from Gitea. Existing testers need to update
|
||||
the custom-repo URL once
|
||||
- Plugin-load time in this release sits at ~3.7 s median (5 reloads), comparable to v1.4.2. Async
|
||||
migration is foundational for v1.4.4 Lazy-Init optimisations rather than an immediate
|
||||
user-perceived win
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -214,17 +381,21 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.2 — ChatLog Frame-Hot-Path
|
||||
|
||||
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations from the chat-log render path eliminated.
|
||||
Third sub-patch of the v1.4.x Polish Sweep series. Per-frame allocations from the chat-log render
|
||||
path eliminated.
|
||||
|
||||
- `DrawMessages` card-mode hoists `theme`/`drawList`/`winLeft`/`winRight`/`borderColorAbgr` out of the per-message loop.
|
||||
About 500 redundant calls per frame at 100 visible messages, multiplied by every pop-out window
|
||||
- Auto-tell tab tint and icon use a per-tab cache. Hash computation and string allocation only happen when the tell
|
||||
target name or world drifts. `AutoTellTabTint` stays a pure hash helper; cache lives in a thin `TabTintCache` wrapper
|
||||
- Status bar gates its tab aggregation behind the same one-second cache it already used for the format strings. LINQ
|
||||
`Sum` and `Count` replaced with a single `foreach` pass that runs on roughly 1 % of frames
|
||||
- `DrawMessages` card-mode hoists `theme`/`drawList`/`winLeft`/`winRight`/`borderColorAbgr` out of
|
||||
the per-message loop. About 500 redundant calls per frame at 100 visible messages, multiplied by
|
||||
every pop-out window
|
||||
- Auto-tell tab tint and icon use a per-tab cache. Hash computation and string allocation only
|
||||
happen when the tell target name or world drifts. `AutoTellTabTint` stays a pure hash helper;
|
||||
cache lives in a thin `TabTintCache` wrapper
|
||||
- Status bar gates its tab aggregation behind the same one-second cache it already used for the
|
||||
format strings. LINQ `Sum` and `Count` replaced with a single `foreach` pass that runs on roughly
|
||||
1 % of frames
|
||||
|
||||
Realistic frame-time recovery: 2-5 % in typical scenes, more on pop-out-heavy setups because the card-border hoist
|
||||
scales per window.
|
||||
Realistic frame-time recovery: 2-5 % in typical scenes, more on pop-out-heavy setups because the
|
||||
card-border hoist scales per window.
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -234,23 +405,24 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.1 — Theme Engine Performance
|
||||
|
||||
Second sub-patch of the v1.4.x Polish Sweep series. Heap pressure from the theme engine's per-frame render path removed,
|
||||
plus a tenth built-in theme and hardening for the custom-theme hot-reload.
|
||||
Second sub-patch of the v1.4.x Polish Sweep series. Heap pressure from the theme engine's per-frame
|
||||
render path removed, plus a tenth built-in theme and hardening for the custom-theme hot-reload.
|
||||
|
||||
- Theme records carry a pre-computed ABGR-packed cache for every color slot; cache is filled when the theme is
|
||||
registered and refreshed defensively on every `Switch()`
|
||||
- `HellionStyle.PushGlobal` reads ABGR values from the cache instead of calling `ColourUtil.RgbaToAbgr` per slot per
|
||||
frame; ~13 % render-time recovery measured in typical scenes (plan estimate was 2–6 %, real ~10–15 %)
|
||||
- `ThemeRegistry` custom-theme reload distinguishes a recoverable file lock (editor mid-save) from a permanent IO
|
||||
failure; locked themes keep their last-known-good snapshot and retry on the next lookup instead of dropping out of the
|
||||
picker
|
||||
- New built-in: **Synthwave Sunset** — Hot Magenta + Cyan on midnight violet, 80s neon-grid vibes; tenth theme in the
|
||||
picker
|
||||
- Author credits refreshed: brand themes are credited as "Hellion Forge"; **Mint Grove** and **Forge Merchantman** now
|
||||
credited to **Carla Beleandis** as a community thanks
|
||||
- Theme records carry a pre-computed ABGR-packed cache for every color slot; cache is filled when
|
||||
the theme is registered and refreshed defensively on every `Switch()`
|
||||
- `HellionStyle.PushGlobal` reads ABGR values from the cache instead of calling
|
||||
`ColourUtil.RgbaToAbgr` per slot per frame; ~13 % render-time recovery measured in typical scenes
|
||||
(plan estimate was 2–6 %, real ~10–15 %)
|
||||
- `ThemeRegistry` custom-theme reload distinguishes a recoverable file lock (editor mid-save) from a
|
||||
permanent IO failure; locked themes keep their last-known-good snapshot and retry on the next
|
||||
lookup instead of dropping out of the picker
|
||||
- New built-in: **Synthwave Sunset** — Hot Magenta + Cyan on midnight violet, 80s neon-grid vibes;
|
||||
tenth theme in the picker
|
||||
- Author credits refreshed: brand themes are credited as "Hellion Forge"; **Mint Grove** and **Forge
|
||||
Merchantman** now credited to **Carla Beleandis** as a community thanks
|
||||
|
||||
No schema bump, no user-visible behaviour change other than smoother frames on GC-sensitive setups and one additional
|
||||
colour option.
|
||||
No schema bump, no user-visible behaviour change other than smoother frames on GC-sensitive setups
|
||||
and one additional colour option.
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -260,20 +432,20 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.4.0 — Critical Lifecycle Fixes
|
||||
|
||||
First sub-patch of the v1.4.x Polish Sweep series. Seven known lifecycle and race bugs eliminated before any performance
|
||||
refactor sits on top.
|
||||
First sub-patch of the v1.4.x Polish Sweep series. Seven known lifecycle and race bugs eliminated
|
||||
before any performance refactor sits on top.
|
||||
|
||||
- MessageStore disposal no longer triggers GC.Collect globally; Pooling=false on the SQLite connection means there's
|
||||
nothing left to clean up by hand
|
||||
- PendingMessage and RetentionSweep worker threads are explicitly marked IsBackground=true so the plugin domain can
|
||||
unload during XIVLauncher reload without waiting for them
|
||||
- EmoteCache image and gif loaders moved from async-void to async Task with a shared task tracker, draining on Dispose
|
||||
so an in-flight load can no longer write to a disposed EmoteImages entry
|
||||
- MessageStore disposal no longer triggers GC.Collect globally; Pooling=false on the SQLite
|
||||
connection means there's nothing left to clean up by hand
|
||||
- PendingMessage and RetentionSweep worker threads are explicitly marked IsBackground=true so the
|
||||
plugin domain can unload during XIVLauncher reload without waiting for them
|
||||
- EmoteCache image and gif loaders moved from async-void to async Task with a shared task tracker,
|
||||
draining on Dispose so an in-flight load can no longer write to a disposed EmoteImages entry
|
||||
- DisposeAsync 10s timeout now warns loudly instead of silently leaving the worker behind
|
||||
- Plugin.Dispose flushes any pending DeferredSaveFrames before tearing services down, so settings changes made in the
|
||||
last few frames before disable are no longer lost
|
||||
- The v13→v14 config migration now reads the pre-v13 backup and carries HellionThemeWindowOpacity into the new
|
||||
WindowOpacity field instead of falling back to the default 0.85
|
||||
- Plugin.Dispose flushes any pending DeferredSaveFrames before tearing services down, so settings
|
||||
changes made in the last few frames before disable are no longer lost
|
||||
- The v13→v14 config migration now reads the pre-v13 backup and carries HellionThemeWindowOpacity
|
||||
into the new WindowOpacity field instead of falling back to the default 0.85
|
||||
|
||||
Modding & support: join Hellion Forge — <https://discord.gg/X9V7Kcv5gR>
|
||||
|
||||
@@ -283,14 +455,14 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
## Hellion Chat 1.3.0 — Plugin Integrations: Honorific
|
||||
|
||||
First step on the plugin-integration roadmap. HellionChat now listens to Honorific and shows your custom title in the
|
||||
chat header. The slot auto-hides when Honorific is not installed, when no custom title is active, or when you are using
|
||||
the original FFXIV title.
|
||||
First step on the plugin-integration roadmap. HellionChat now listens to Honorific and shows your
|
||||
custom title in the chat header. The slot auto-hides when Honorific is not installed, when no custom
|
||||
title is active, or when you are using the original FFXIV title.
|
||||
|
||||
- New "Integrations" settings tab
|
||||
- Honorific integration with auto-detection and live updates
|
||||
- "Coming soon" preview of the next five planned integrations: context menu actions, smart notifications, RP status
|
||||
block, ExtraChat channels, and quick DM compose
|
||||
- "Coming soon" preview of the next five planned integrations: context menu actions, smart
|
||||
notifications, RP status block, ExtraChat channels, and quick DM compose
|
||||
- Maintainer attribution buttons for Honorific repo and Caraxi
|
||||
- New service-class pattern under HellionChat/Integrations/
|
||||
|
||||
@@ -305,20 +477,22 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
### Added
|
||||
|
||||
- Four new built-in themes:
|
||||
- **Night Blue** — Royal Blue on deep marine, cool tech-dashboard mood
|
||||
- **Indigo Violet** — Royal Violet on deep indigo with a turquoise-mint counter (aurora glitter feel)
|
||||
- **Forge Merchantman** — Patina bronze on workshop slate with warm amber counter (Hellion Forge identity)
|
||||
- **Hellion Spectrum** — Deuteran/Protan-safe channel colours using Wong/Okabe-Ito palette tones; channel identity
|
||||
(Tell pink, Yell yellow, Shout orange, Party blue, FC green) preserved while keeping every channel separable under
|
||||
red-green colour vision deficiency
|
||||
- **Night Blue** — Royal Blue on deep marine, cool tech-dashboard mood
|
||||
- **Indigo Violet** — Royal Violet on deep indigo with a turquoise-mint counter (aurora glitter
|
||||
feel)
|
||||
- **Forge Merchantman** — Patina bronze on workshop slate with warm amber counter (Hellion Forge
|
||||
identity)
|
||||
- **Hellion Spectrum** — Deuteran/Protan-safe channel colours using Wong/Okabe-Ito palette tones;
|
||||
channel identity (Tell pink, Yell yellow, Shout orange, Party blue, FC green) preserved while
|
||||
keeping every channel separable under red-green colour vision deficiency
|
||||
- Built-in theme catalogue grown from five to nine
|
||||
|
||||
### Notes
|
||||
|
||||
- No engine changes, no settings touched, no migration
|
||||
- Default theme unchanged (Hellion Arctic). Existing custom themes keep working.
|
||||
- Hellion Spectrum covers the ~99 % of CVD cases that are red-green; a Tritan-safe variant could follow in a later cycle
|
||||
if there is demand.
|
||||
- Hellion Spectrum covers the ~99 % of CVD cases that are red-green; a Tritan-safe variant could
|
||||
follow in a later cycle if there is demand.
|
||||
|
||||
---
|
||||
|
||||
@@ -326,21 +500,23 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
### Changed
|
||||
|
||||
- Settings cards re-sorted thematically: 9 cards remain, each card has one clear job and a one-line subtitle.
|
||||
- **Theme & Layout** (new) collects the theme picker, window frame style (title bar, sidebar, hide button, pop-out title
|
||||
bar) and the timestamp style options.
|
||||
- Settings cards re-sorted thematically: 9 cards remain, each card has one clear job and a one-line
|
||||
subtitle.
|
||||
- **Theme & Layout** (new) collects the theme picker, window frame style (title bar, sidebar, hide
|
||||
button, pop-out title bar) and the timestamp style options.
|
||||
- **Fonts & Colours** (new) houses font choice, font size and per-channel chat colours.
|
||||
- **Data Management** (new) collects retention windows, cleanup, export, the database viewer and the shift-click
|
||||
advanced tools.
|
||||
- **Data Management** (new) collects retention windows, cleanup, export, the database viewer and the
|
||||
shift-click advanced tools.
|
||||
- **Privacy** is now focused on the privacy filter alone.
|
||||
- **Chat** absorbs the Auto-Tell-Tabs history-preload slider that used to live under Privacy.
|
||||
- **General** groups the keybind mode under Input.
|
||||
|
||||
### Removed
|
||||
|
||||
- Legacy "Style override" option and the unused style-name field — both obsolete since the v1.1.0 themes engine.
|
||||
- Legacy `WindowAlpha` slider — if you had it set, the value is auto-migrated to Theme & Layout → Window Style → Window
|
||||
Transparency.
|
||||
- Legacy "Style override" option and the unused style-name field — both obsolete since the v1.1.0
|
||||
themes engine.
|
||||
- Legacy `WindowAlpha` slider — if you had it set, the value is auto-migrated to Theme & Layout →
|
||||
Window Style → Window Transparency.
|
||||
- Unused `ShowThemeQuickPicker` schema field.
|
||||
|
||||
### Migration
|
||||
@@ -355,82 +531,93 @@ Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
### Added
|
||||
|
||||
- Sidebar tab modernization: icon-only at fixed 44 px, tooltip on hover, vertical accent pill for active tab
|
||||
- Sidebar tab modernization: icon-only at fixed 44 px, tooltip on hover, vertical accent pill for
|
||||
active tab
|
||||
- Top tabs: accent underline pill replaces background fill on active tab
|
||||
- Per-tab custom icons in Settings → Tabs (15-glyph FontAwesome picker)
|
||||
- Bottom status bar (22 px): channel indicator, privacy badge, counters, tells, version — updates 1×/sec
|
||||
- Bottom status bar (22 px): channel indicator, privacy badge, counters, tells, version — updates
|
||||
1×/sec
|
||||
- Card rows as default message render: sender header in channel color, subtle border between cards
|
||||
- Compact-Density toggle in Appearance: switches back to single-line `[HH:mm] Sender: Text` layout
|
||||
- Auto-Tell tabs: per-partner hashed icon (7-glyph pool: envelope/star/heart/bell/bookmark/flag/fire) plus hashed color
|
||||
(12-color palette) — 84 distinct icon+color combinations
|
||||
- Unread indicator: pulsing red dot in the top-right corner of any sidebar tab icon with unread messages, 2-second
|
||||
sine-wave pulse, respects `Configuration.ReduceMotion`
|
||||
- Auto-Tell tabs: per-partner hashed icon (7-glyph pool:
|
||||
envelope/star/heart/bell/bookmark/flag/fire) plus hashed color (12-color palette) — 84 distinct
|
||||
icon+color combinations
|
||||
- Unread indicator: pulsing red dot in the top-right corner of any sidebar tab icon with unread
|
||||
messages, 2-second sine-wave pulse, respects `Configuration.ReduceMotion`
|
||||
|
||||
### Changed
|
||||
|
||||
- Migration v14 → v15: deprecated Configuration fields `HellionThemeEnabled` and `HellionThemeWindowOpacity` removed
|
||||
- Appearance settings cleaned: legacy theme-engine bindings replaced by Themes tab (introduced in v1.1.0)
|
||||
- Migration v14 → v15: deprecated Configuration fields `HellionThemeEnabled` and
|
||||
`HellionThemeWindowOpacity` removed
|
||||
- Appearance settings cleaned: legacy theme-engine bindings replaced by Themes tab (introduced in
|
||||
v1.1.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Settings save no longer wipes chat history by default — the heavy `ClearAllTabs + FilterAllTabsAsync` cycle now only
|
||||
runs when a filter-relevant setting actually changed (Privacy filter, persisted channels, per-tab channel selection).
|
||||
Cosmetic changes keep the in-session chat intact
|
||||
- Settings save no longer wipes chat history by default — the heavy
|
||||
`ClearAllTabs + FilterAllTabsAsync` cycle now only runs when a filter-relevant setting actually
|
||||
changed (Privacy filter, persisted channels, per-tab channel selection). Cosmetic changes keep the
|
||||
in-session chat intact
|
||||
- Identifier-based `MessageList` restore in `Configuration.UpdateFrom` plus TempTab skip in
|
||||
`ClearAllTabs`/`FilterAllTabs` ensure persistent tabs and Auto-Tell tabs both survive the save
|
||||
- Sidebar buttons now align vertically with the first message row (top padding mirrors the chat header toolbar height)
|
||||
- Sidebar buttons now align vertically with the first message row (top padding mirrors the chat
|
||||
header toolbar height)
|
||||
- Sidebar child window no longer paints the top padding area with its frame background
|
||||
- Status bar version slot (`vX.Y.Z · Hellion`) no longer clips its rightmost character
|
||||
|
||||
### Notes
|
||||
|
||||
- Polish phase (animations, theme crossfade, header quick-picker) follows in v1.3.0
|
||||
- Top-Tab icon prefixes were considered but dropped: Dalamud's default font atlas does not include FontAwesome
|
||||
codepoints, so mixed-font in a single TabItem label renders as tofu. Underline pill alone is the v1.2.0 visual
|
||||
treatment for top tabs. Resolution would require Font-Atlas merge at FontManager level — out of scope.
|
||||
- Top-Tab icon prefixes were considered but dropped: Dalamud's default font atlas does not include
|
||||
FontAwesome codepoints, so mixed-font in a single TabItem label renders as tofu. Underline pill
|
||||
alone is the v1.2.0 visual treatment for top tabs. Resolution would require Font-Atlas merge at
|
||||
FontManager level — out of scope.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.0] — 2026-05-05 — Theme Foundation
|
||||
|
||||
First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom themes via JSON, settings card grid.
|
||||
First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom themes via JSON,
|
||||
settings card grid.
|
||||
|
||||
### Added
|
||||
|
||||
- **Theme engine** with five built-in themes: Hellion Arctic (default), Chat 2 Classic, Event Horizon, Moonlit Bloom,
|
||||
Mint Grove.
|
||||
- **Settings → Themes** with mini mockup preview per theme. Clicking a card instantly switches the entire plugin (chat,
|
||||
settings, pop-outs).
|
||||
- **Custom themes via JSON** in `pluginConfigs/HellionChat/themes/`. On first start, `example-theme.json` is placed
|
||||
there as a template.
|
||||
- **Optional theme chat channel colours**: themes can ship their own channel colours. On switch, a banner appears with
|
||||
_Apply / Keep current_ — never applied automatically.
|
||||
- **Settings card grid**: new overview on open, clicking a card navigates into the section's detail view. Breadcrumb +
|
||||
ESC navigate back.
|
||||
- **Theme engine** with five built-in themes: Hellion Arctic (default), Chat 2 Classic, Event
|
||||
Horizon, Moonlit Bloom, Mint Grove.
|
||||
- **Settings → Themes** with mini mockup preview per theme. Clicking a card instantly switches the
|
||||
entire plugin (chat, settings, pop-outs).
|
||||
- **Custom themes via JSON** in `pluginConfigs/HellionChat/themes/`. On first start,
|
||||
`example-theme.json` is placed there as a template.
|
||||
- **Optional theme chat channel colours**: themes can ship their own channel colours. On switch, a
|
||||
banner appears with _Apply / Keep current_ — never applied automatically.
|
||||
- **Settings card grid**: new overview on open, clicking a card navigates into the section's detail
|
||||
view. Breadcrumb + ESC navigate back.
|
||||
- **`docs/THEME-AUTHORING.md`** as a guide for writing custom themes, with Hellion Forge branding.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Plugin icon** updated to Hellion Forge hammer (previously a ChatTwo derivative).
|
||||
- **Settings detail view** uses the full width — the second tab list on the left is gone because the card overview
|
||||
handles navigation.
|
||||
- **`HellionStyle.PushGlobal`** is now theme-driven (`PushGlobal(theme, opacity)`) instead of const-palette-driven.
|
||||
- **Configuration v13 → v14**: all users land on `hellion-arctic`. Those who prefer the upstream look can select
|
||||
`chat2-classic` in Settings → Themes.
|
||||
- **Settings detail view** uses the full width — the second tab list on the left is gone because the
|
||||
card overview handles navigation.
|
||||
- **`HellionStyle.PushGlobal`** is now theme-driven (`PushGlobal(theme, opacity)`) instead of
|
||||
const-palette-driven.
|
||||
- **Configuration v13 → v14**: all users land on `hellion-arctic`. Those who prefer the upstream
|
||||
look can select `chat2-classic` in Settings → Themes.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- `Configuration.HellionThemeEnabled` and `HellionThemeWindowOpacity` remain readable for one release as a safety net
|
||||
but are no longer evaluated. Removal planned for v1.2.0.
|
||||
- `Configuration.HellionThemeEnabled` and `HellionThemeWindowOpacity` remain readable for one
|
||||
release as a safety net but are no longer evaluated. Removal planned for v1.2.0.
|
||||
|
||||
### Security
|
||||
|
||||
- Custom theme JSON loader validates `schemaVersion`, required fields and hex format. Invalid themes are skipped with a
|
||||
warning; the plugin continues loading with built-ins.
|
||||
- Custom theme JSON loader validates `schemaVersion`, required fields and hex format. Invalid themes
|
||||
are skipped with a warning; the plugin continues loading with built-ins.
|
||||
|
||||
### Internal
|
||||
|
||||
- 51 local unit tests (theme records, registry, JSON round-trip, sanity per built-in theme). Tests are gitignored.
|
||||
- 51 local unit tests (theme records, registry, JSON round-trip, sanity per built-in theme). Tests
|
||||
are gitignored.
|
||||
|
||||
---
|
||||
|
||||
@@ -438,18 +625,20 @@ First major UI cycle after v1.0.0. Theme engine, five built-in themes, custom th
|
||||
|
||||
Four small polish items from the backlog bundled together:
|
||||
|
||||
- **Hide on New Game+ menu**: optional global toggle that hides Hellion Chat (and all other plugin windows such as
|
||||
Settings, DB Viewer, pop-outs) while the NG+ menu is open. Settings → Window → Frame, default off. Skips the entire
|
||||
`WindowSystem.Draw()` path analogous to the existing LoadingScreens pattern.
|
||||
- **Channel selector colouring**: optional tinting of the channel-select button (comment icon) next to the input field
|
||||
in the current channel colour. Settings → Appearance → Chat Colours, default on. Consistent with the existing input
|
||||
text colouring; ExtraChat override is carried over.
|
||||
- **(De)buff icon aspect-ratio fix**: `PayloadHandler.InlineIcon` was squashing all hover icons to 32×32. Status icons
|
||||
with non-square dimensions (debuffs with an arrow indicator) are now shrunk aspect-preserving. Standalone float-math
|
||||
implementation with zero-size guard instead of a cherry-pick from the open ChatTwo PR #157 (which had an int-division
|
||||
trap).
|
||||
- **HideState logging sweep**: all HideState transitions (Battle/Cutscene/User/Override plus pop-out mirroring) log at
|
||||
verbose level. Off by default; enable via `/xllog set HellionChat verbose` for bug-report diagnostics.
|
||||
- **Hide on New Game+ menu**: optional global toggle that hides Hellion Chat (and all other plugin
|
||||
windows such as Settings, DB Viewer, pop-outs) while the NG+ menu is open. Settings → Window →
|
||||
Frame, default off. Skips the entire `WindowSystem.Draw()` path analogous to the existing
|
||||
LoadingScreens pattern.
|
||||
- **Channel selector colouring**: optional tinting of the channel-select button (comment icon) next
|
||||
to the input field in the current channel colour. Settings → Appearance → Chat Colours, default
|
||||
on. Consistent with the existing input text colouring; ExtraChat override is carried over.
|
||||
- **(De)buff icon aspect-ratio fix**: `PayloadHandler.InlineIcon` was squashing all hover icons to
|
||||
32×32. Status icons with non-square dimensions (debuffs with an arrow indicator) are now shrunk
|
||||
aspect-preserving. Standalone float-math implementation with zero-size guard instead of a
|
||||
cherry-pick from the open ChatTwo PR #157 (which had an int-division trap).
|
||||
- **HideState logging sweep**: all HideState transitions (Battle/Cutscene/User/Override plus pop-out
|
||||
mirroring) log at verbose level. Off by default; enable via `/xllog set HellionChat verbose` for
|
||||
bug-report diagnostics.
|
||||
|
||||
[Release Notes 1.0.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.3)
|
||||
|
||||
@@ -457,14 +646,14 @@ Four small polish items from the backlog bundled together:
|
||||
|
||||
## [1.0.1] — 2026-05-04 — Window Position Recovery
|
||||
|
||||
Fixes an off-screen-window scenario the user could end up in after a monitor disconnect or display layout change between
|
||||
sessions. An automatic one-shot bounds check on the first draw after plugin load snaps the window back into the visible
|
||||
viewport, and a new "Reset Window Position" button in Settings → Window → Frame serves as the manual escape hatch for
|
||||
edge cases.
|
||||
Fixes an off-screen-window scenario the user could end up in after a monitor disconnect or display
|
||||
layout change between sessions. An automatic one-shot bounds check on the first draw after plugin
|
||||
load snaps the window back into the visible viewport, and a new "Reset Window Position" button in
|
||||
Settings → Window → Frame serves as the manual escape hatch for edge cases.
|
||||
|
||||
Bundled housekeeping since v1.0.0: documentation restructured into `docs/`, stale ChatTwo/\* paths in repo configs
|
||||
cleaned up, Pidgin parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps for `actions/setup-dotnet` (4 → 5)
|
||||
and `github/codeql-action` (3 → 4).
|
||||
Bundled housekeeping since v1.0.0: documentation restructured into `docs/`, stale ChatTwo/\* paths
|
||||
in repo configs cleaned up, Pidgin parser library bumped from 3.3.0 to 3.5.1, GitHub Actions bumps
|
||||
for `actions/setup-dotnet` (4 → 5) and `github/codeql-action` (3 → 4).
|
||||
|
||||
[Release Notes 1.0.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.1)
|
||||
|
||||
@@ -472,11 +661,11 @@ and `github/codeql-action` (3 → 4).
|
||||
|
||||
## [1.0.0] — 2026-05-03 — Standalone Major Release
|
||||
|
||||
First fully independent release. Code namespace, IPC channels and source tree structure consolidated under
|
||||
`HellionChat.*`. Plugin refuses to start alongside an active upstream Chat 2 (bilingual conflict message). SQLite native
|
||||
pinned to 3.50.3 (CVE-2025-6965, CVE-2025-7709). Tab layout default for new installs and users on config version 12 or
|
||||
older restructured (5 thematic tabs instead of 6+ kitchen-sink). Sweep of critical and major findings from the codebase
|
||||
audit incorporated.
|
||||
First fully independent release. Code namespace, IPC channels and source tree structure consolidated
|
||||
under `HellionChat.*`. Plugin refuses to start alongside an active upstream Chat 2 (bilingual
|
||||
conflict message). SQLite native pinned to 3.50.3 (CVE-2025-6965, CVE-2025-7709). Tab layout default
|
||||
for new installs and users on config version 12 or older restructured (5 thematic tabs instead of 6+
|
||||
kitchen-sink). Sweep of critical and major findings from the codebase audit incorporated.
|
||||
|
||||
[Release Notes 1.0.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v1.0.0)
|
||||
|
||||
@@ -484,9 +673,9 @@ audit incorporated.
|
||||
|
||||
## [0.6.1] — 2026-05-03 — Pop-Out Discoverability & /tell Auto-Pop-Out
|
||||
|
||||
Pop-out button visible in the chat header, one-time hint banner for the pop-out feature. New setting "Open new /tell
|
||||
tabs directly as pop-out". Pop-out input is now active by default. Bug fixes: ghost windows on LRU-drop / logout, dead
|
||||
zone below the input bar when the hint banner is active.
|
||||
Pop-out button visible in the chat header, one-time hint banner for the pop-out feature. New setting
|
||||
"Open new /tell tabs directly as pop-out". Pop-out input is now active by default. Bug fixes: ghost
|
||||
windows on LRU-drop / logout, dead zone below the input bar when the hint banner is active.
|
||||
|
||||
[Release Notes 0.6.1](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.1)
|
||||
|
||||
@@ -494,9 +683,10 @@ zone below the input bar when the hint banner is active.
|
||||
|
||||
## [0.6.0] — 2026-05-03 — UX Polish: Pop-Out Input + Colour Presets
|
||||
|
||||
Two opt-in UX features. Pop-out windows optionally get a compact input bar with a channel-coloured icon button and an
|
||||
independent text buffer per pop-out. Seven built-in colour presets (Classic, High Contrast, Pastel, Dark Mode Tuned,
|
||||
Hellion, Night Blue, Indigo Violet) for one-click apply. Configuration migration v10 → v11.
|
||||
Two opt-in UX features. Pop-out windows optionally get a compact input bar with a channel-coloured
|
||||
icon button and an independent text buffer per pop-out. Seven built-in colour presets (Classic, High
|
||||
Contrast, Pastel, Dark Mode Tuned, Hellion, Night Blue, Indigo Violet) for one-click apply.
|
||||
Configuration migration v10 → v11.
|
||||
|
||||
[Release Notes 0.6.0](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.6.0)
|
||||
|
||||
@@ -504,9 +694,9 @@ Hellion, Night Blue, Indigo Violet) for one-click apply. Configuration migration
|
||||
|
||||
## [0.5.4] — 2026-05-02 — WrapText Hardening
|
||||
|
||||
`ImGuiUtil.WrapText` rewritten from pointer arithmetic to Span- and index-based control flow. Permanently closes the
|
||||
recurring CodeQL critical alert "unvalidated local pointer arithmetic". No user-visible behaviour change — word-wrap
|
||||
output is byte-identical to 0.5.3.
|
||||
`ImGuiUtil.WrapText` rewritten from pointer arithmetic to Span- and index-based control flow.
|
||||
Permanently closes the recurring CodeQL critical alert "unvalidated local pointer arithmetic". No
|
||||
user-visible behaviour change — word-wrap output is byte-identical to 0.5.3.
|
||||
|
||||
[Release Notes 0.5.4](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.4)
|
||||
|
||||
@@ -514,8 +704,8 @@ output is byte-identical to 0.5.3.
|
||||
|
||||
## [0.5.3] — 2026-05-02 — Pointer Arithmetic Hardening
|
||||
|
||||
First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Encoded byte buffer length is validated via
|
||||
`GetByteCount` before pointer arithmetic.
|
||||
First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Encoded byte buffer
|
||||
length is validated via `GetByteCount` before pointer arithmetic.
|
||||
|
||||
[Release Notes 0.5.3](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases/tag/v0.5.3)
|
||||
|
||||
@@ -523,7 +713,8 @@ First attempt at closing the CodeQL critical alert in `ImGuiUtil.WrapText`. Enco
|
||||
|
||||
## Earlier Versions
|
||||
|
||||
Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on the Gitea release stream:
|
||||
Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on the Gitea release
|
||||
stream:
|
||||
|
||||
[All Releases](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/releases)
|
||||
|
||||
@@ -531,6 +722,7 @@ Releases before 0.5.3 (bootstrap phase 0.1.0 to 0.5.2) are available directly on
|
||||
|
||||
## Maintenance Note
|
||||
|
||||
The source of truth for the user-facing changelog is the `changelog:` block in `HellionChat/HellionChat.yaml`.
|
||||
`repo.json` and the GitHub release body are fed from there. This file (`docs/CHANGELOG.md`) is a curated summary with
|
||||
links to the release pages and is updated manually on each version bump.
|
||||
The source of truth for the user-facing changelog is the `changelog:` block in
|
||||
`HellionChat/HellionChat.yaml`. `repo.json` and the GitHub release body are fed from there. This
|
||||
file (`docs/CHANGELOG.md`) is a curated summary with links to the release pages and is updated
|
||||
manually on each version bump.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user