Compare commits
251 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eead8d813c | |||
| 28b20ad6d3 | |||
| a88ec1714d | |||
| 0110295e7d | |||
| 9752206996 | |||
| 2f4e4c33ca | |||
| b30b6b135c | |||
| df0844b737 | |||
| 21d703bf0b | |||
| 4048f0b8d0 | |||
| 2d0e9ae70c | |||
| eaf11dcebe | |||
| 9bd8262191 | |||
| ddb00a0836 | |||
| aec8ba15f2 | |||
| c84eae199b | |||
| 9ead8098f5 | |||
| b190456005 | |||
| ebc0999a8e | |||
| c0b3edb20c | |||
| 64cadcf87b | |||
| 0165cba966 | |||
| 3da550c2fc | |||
| 4b43fdb0ad | |||
| 56621669b2 | |||
| ed2a0f7374 | |||
| 59e86cd8dd | |||
| a74e3da030 | |||
| b8ed2a1ce5 | |||
| e6c6c02780 | |||
| ab9ebedeee | |||
| 11af4ce4c4 | |||
| 8a78390a15 | |||
| 23e47e06c0 | |||
| ff60576f3c | |||
| 5b5bacfc41 | |||
| eb8b7be2f5 | |||
| eb05e04f79 | |||
| 2f0affcdbb | |||
| dfa7c47887 | |||
| acf799440e | |||
| 3e98b9103f | |||
| 4a613f7acb | |||
| af5f4d380a | |||
| ecf1e93a1b | |||
| e404a2e0d9 | |||
| d485f5ea1f | |||
| b48684ce5a | |||
| a11c8bc6e9 | |||
| 985a284e7d | |||
| e629518550 | |||
| c28c972ae3 | |||
| bc0f44712f | |||
| f663cb3c14 | |||
| 5a9c2018b0 | |||
| a1cdae05d0 | |||
| c17f5ae516 | |||
| a2db8cb639 | |||
| 507efc8cda | |||
| 6f3cf2f3ce | |||
| c979a05d6c | |||
| c53e453341 | |||
| 2519b413f8 | |||
| e5ac4faf7b | |||
| 0c26d1aa67 | |||
| 8b13ba1fdc | |||
| 52da5d5e23 | |||
| 916640fb60 | |||
| feeb1df4eb | |||
| f2086865ce | |||
| 15a89dd6e7 | |||
| 53952717c0 | |||
| fcbbd174b6 | |||
| d41cea0031 | |||
| c943a2cff3 | |||
| abcd0847ef | |||
| 2f52cbb7d4 | |||
| 9103bbb892 | |||
| 8f9c01d322 | |||
| af4651b37e | |||
| 485dc4e1b4 | |||
| c878d24d11 | |||
| cb5c940a84 | |||
| dd3a0ea069 | |||
| 4bf6c3ef1f | |||
| 2378ce6bf2 | |||
| b85db24601 | |||
| cae7d76206 | |||
| 4c6d52e652 | |||
| cbfdfe35be | |||
| 537b96c79f | |||
| d3d28924e6 | |||
| 48f1fb5ba1 | |||
| 0b13efd0b5 | |||
| 289fe2eb78 | |||
| fe9e66b0ff | |||
| 990edd8300 | |||
| db95ec7dff | |||
| 7e036c1d00 | |||
| 1c511a147d | |||
| f093d93761 | |||
| e7c8667497 | |||
| 497197eb2c | |||
| 08b2ffc600 | |||
| 8db3eca46c | |||
| 4d54eabdac | |||
| 698eb01bbe | |||
| a3fbaab173 | |||
| 57291e925d | |||
| 8e9332ac8c | |||
| fcb72e2b78 | |||
| 7012e8c0d8 | |||
| 176474ec2a | |||
| 9fc8749d15 | |||
| 09634b416d | |||
| 393ef175bf | |||
| d63c710836 | |||
| fa9baa3929 | |||
| 4c18b9a62b | |||
| 26c12c3410 | |||
| 76a4de1192 | |||
| 55aeaea5b9 | |||
| e6d25f3e38 | |||
| 740c7cf1bb | |||
| 71f0b63079 | |||
| 8ee54bb8df | |||
| e3ce41306e | |||
| af7c757e63 | |||
| a10c115b9b | |||
| 6d49dbad3e | |||
| a651b3b9ad | |||
| 3f2e56be67 | |||
| feb6e262e4 | |||
| 1d557f1b0e | |||
| fea4965889 | |||
| 91663832f0 | |||
| 9cf1b19801 | |||
| 1f7f0945c5 | |||
| cd6afb32cb | |||
| 7d5496e959 | |||
| ed426556e1 | |||
| 96c445356b | |||
| 1c2d361b77 | |||
| 581aae1735 | |||
| 70109e1896 | |||
| 0df5819a88 | |||
| 3fbbe8543f | |||
| 03dfb8e3da | |||
| a987e97610 | |||
| ecd46ed630 | |||
| f2f7599f81 | |||
| ac158907ea | |||
| 9506af49db | |||
| c882eac1ca | |||
| 7a6b44048a | |||
| 0d39d59a04 | |||
| f0e0db55e3 | |||
| f207239d56 | |||
| ccf2ec9f12 | |||
| aff7a5e7ce | |||
| cd84ca2b3f | |||
| 7c645afa1d | |||
| 24c1e0e754 | |||
| 9f6a0807d1 | |||
| 15f83c8b0e | |||
| c7253bdf02 | |||
| cf10c566dd | |||
| acfe838bc6 | |||
| 9e1f559644 | |||
| 2c79a67dae | |||
| 1687271bfd | |||
| cb5457ba2e | |||
| a701f6c103 | |||
| 8cad8651d2 | |||
| 61b547606c | |||
| 059cfa6e28 | |||
| 71d84e4486 | |||
| 92301869ed | |||
| c3d06a9c94 | |||
| 911c870e24 | |||
| 8cda19d993 | |||
| 62621ba855 | |||
| 497c259031 | |||
| 9ad9d2acd2 | |||
| 1b63765caa | |||
| 61764459ed | |||
| 1b7f2c40e6 | |||
| 93d52ae819 | |||
| 48b3d5c6b1 | |||
| e9a9d8a01c | |||
| a155a57f33 | |||
| 90b83a0690 | |||
| f10301c3e4 | |||
| 8571a936a4 | |||
| 3f6144836c | |||
| 53c432a635 | |||
| 340cadf3b9 | |||
| 8d6868aef6 | |||
| 6e8fcc8cc3 | |||
| 57670ffc76 | |||
| 2144eedd76 | |||
| 43daef83de | |||
| 4a9ad426e7 | |||
| 13beda3a8d | |||
| 18c05af4db | |||
| df6e1e1cbd | |||
| 01b1a14511 | |||
| b6af8d559c | |||
| 22dbfc2e24 | |||
| 2f3b01732c | |||
| 88803382dd | |||
| 74c51163c7 | |||
| 877ff4ba18 | |||
| ad2feb5a27 | |||
| 46b63ffdd1 | |||
| 4ba5004322 | |||
| 3584c94523 | |||
| 303729f3d3 | |||
| 12085ff1e2 | |||
| e4593a0fda | |||
| 3fc42963ae | |||
| 7c52e890e6 | |||
| 4d977d5118 | |||
| ddd72a878e | |||
| 66450dd518 | |||
| 7de28ef9b2 | |||
| da3c1f6832 | |||
| e66ae1f5b4 | |||
| 281a1e172f | |||
| 45a5035426 | |||
| e1931fc7d2 | |||
| 2201478a54 | |||
| 50963ccf1b | |||
| fde85e6d69 | |||
| c22b169b73 | |||
| 6839ccaf34 | |||
| fa108c2271 | |||
| 395a0d7c98 | |||
| b76bfb3cfc | |||
| 0512e4729c | |||
| 654f24c609 | |||
| 0e2a14197c | |||
| 52e163a472 | |||
| e086afe2a8 | |||
| c97ce7543b | |||
| cca4571470 | |||
| 444d7f8e2e | |||
| 71ae95d79c | |||
| 9a38f7f094 | |||
| c33e519bb9 | |||
| 14e585ef63 |
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Generated files
|
||||
ChatTwo/Resources/Language.*.resx linguist-generated=true
|
||||
HellionChat/Resources/Language.*.resx linguist-generated=true
|
||||
@@ -0,0 +1,13 @@
|
||||
# HellionChat is a hobby project and does not solicit funding.
|
||||
#
|
||||
# If you want to support the work that made HellionChat possible,
|
||||
# please consider supporting the upstream Chat 2 maintainers:
|
||||
#
|
||||
# Infiziert90 (Infi): https://ko-fi.com/infiii
|
||||
# Anna Clemens: https://ko-fi.com/lojewalo
|
||||
#
|
||||
# Both Ko-fi pages are also linked in the plugin's settings panel.
|
||||
|
||||
# No platforms enabled — keep this file present so GitHub recognises
|
||||
# the project as having considered funding without showing a Sponsor
|
||||
# button on the repository page.
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Bug report
|
||||
description: Something in HellionChat is broken or behaves wrong
|
||||
labels:
|
||||
- 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
|
||||
use the [private vulnerability advisory](https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new)
|
||||
instead.
|
||||
|
||||
- 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
|
||||
- label: I have searched existing issues for duplicates
|
||||
required: true
|
||||
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Security vulnerability
|
||||
url: https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
||||
about: Do not open a public issue for security problems. Use the private advisory 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: Discord
|
||||
url: https://discord.com/users/j.j_kazama
|
||||
about: Quick questions, casual feedback. Bug reports still go through the issue tracker for tracking.
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Feature request
|
||||
description: Suggest a feature or enhancement for HellionChat
|
||||
labels:
|
||||
- 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: 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
|
||||
- label: I understand HellionChat is a privacy-focused fork and not a feature parity tool with upstream Chat 2
|
||||
required: true
|
||||
@@ -0,0 +1,72 @@
|
||||
<!--
|
||||
Thanks for contributing to HellionChat. Please fill in the sections
|
||||
below so the review goes quickly. Delete sections that genuinely do
|
||||
not apply, but do not delete the whole template.
|
||||
|
||||
If this is a security fix, stop here and use a private security
|
||||
advisory instead:
|
||||
https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- One or two sentences. What does this PR change and why. -->
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Tick all that apply. -->
|
||||
|
||||
- [ ] 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)
|
||||
- [ ] Documentation only
|
||||
- [ ] Translation update
|
||||
- [ ] Build, CI or tooling change
|
||||
- [ ] Upstream cherry-pick from Chat 2
|
||||
|
||||
## Linked issue
|
||||
|
||||
<!-- e.g. "Closes #42" or "Refs #42". For trivial typo fixes, "n/a". -->
|
||||
|
||||
## How I tested this
|
||||
|
||||
<!--
|
||||
- Built locally with `dotnet build -c Release`
|
||||
- Ran `dotnet test`
|
||||
- Loaded the plugin in-game on Windows / Linux / macOS via XIVLauncher
|
||||
- Specific scenarios I exercised in-game
|
||||
-->
|
||||
|
||||
## User-visible changes
|
||||
|
||||
<!--
|
||||
Anything the end user will notice. New settings, changed defaults,
|
||||
new commands, new translations, removed behaviour. If none, write
|
||||
"none".
|
||||
-->
|
||||
|
||||
## Compatibility notes
|
||||
|
||||
<!--
|
||||
- Does this require a configuration migration? If yes, which version
|
||||
bump and is it covered by the existing migration tests?
|
||||
- Does this change the schema in MessageStore?
|
||||
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
||||
- Does this affect the upstream cherry-pick path? See docs/UPSTREAM_SYNC.md.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] 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 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)).
|
||||
- [ ] I confirm my contribution is released under the
|
||||
[EUPL-1.2](../LICENSE).
|
||||
@@ -0,0 +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
|
||||
|
||||
# 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)"
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
- Anleitung zum Schreiben eigener Themes: `docs/THEME-AUTHORING.md`
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
|
||||
Animation-Polish (Lerps, Theme-Crossfade, Quick-Picker) folgt in v1.3.0.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
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)
|
||||
- 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`
|
||||
- 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
|
||||
|
||||
Pure UX-Polish, keine neuen Features. Nächster Cycle (v1.3.0): Animation-Polish (Lerps, Theme-Crossfade, Quick-Picker) wie ursprünglich geplant.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
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
|
||||
- **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
|
||||
- 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.
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
---
|
||||
|
||||
## How 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://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/repo.json`
|
||||
3. Enable, save, then `/xlplugins` → search **Hellion Chat** → install
|
||||
|
||||
## Project documents
|
||||
|
||||
- [README](https://github.com/JonKazama-Hellion/HellionChat/blob/main/README.md) — features, architecture, build
|
||||
- [Privacy notice](https://github.com/JonKazama-Hellion/HellionChat/blob/main/PRIVACY.md) — what the plugin stores and sends
|
||||
- [Third-party notices](https://github.com/JonKazama-Hellion/HellionChat/blob/main/docs/THIRD_PARTY_NOTICES.md) — dependencies and licences
|
||||
- [Security policy](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SECURITY.md) — vulnerability reporting
|
||||
- [Support](https://github.com/JonKazama-Hellion/HellionChat/blob/main/SUPPORT.md) — bug reports, questions, contact paths
|
||||
|
||||
## Licence
|
||||
|
||||
[EUPL-1.2](https://github.com/JonKazama-Hellion/HellionChat/blob/main/LICENSE).
|
||||
Based on [Chat 2](https://github.com/Infiziert90/ChatTwo) by Infi and Anna,
|
||||
also EUPL-1.2.
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Build
|
||||
|
||||
# Verifies that every push to main and every PR still builds against the
|
||||
# current Dalamud staging branch. Does not produce release artefacts; the
|
||||
# release workflow handles that on tag.
|
||||
|
||||
on:
|
||||
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
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build (Release)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
shell: pwsh
|
||||
run: |
|
||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||
|
||||
- name: Upload build output
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: HellionChat-build-${{ github.run_number }}
|
||||
path: HellionChat/bin/Release/**/HellionChat/**
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,93 @@
|
||||
name: CodeQL
|
||||
|
||||
# Replaces the GitHub default-setup CodeQL scan. The default setup runs
|
||||
# without resolving the Dalamud assemblies (they live in a user-AppData
|
||||
# path) and reports "Low C# analysis quality" because call-target
|
||||
# resolution sits at ~64%. This workflow downloads the Dalamud staging
|
||||
# distribution before the build, runs a manual dotnet build, and then
|
||||
# lets CodeQL analyse the fully-resolved compilation. Quality climbs
|
||||
# back above the 85% thresholds.
|
||||
#
|
||||
# This workflow only consumes trusted inputs: the tag/branch ref via
|
||||
# the standard checkout action, and the Dalamud distribution URL which
|
||||
# is pinned to a goatcorp-controlled GitHub Pages target. No user-
|
||||
# controlled event payload (issue title, PR body, commit message) flows
|
||||
# into a run-step.
|
||||
#
|
||||
# Disable the default setup in the repo before this workflow lands:
|
||||
# Settings -> Code security -> Code scanning -> "CodeQL analysis" tile
|
||||
# -> Switch to advanced.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '17 6 * * 1'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze-csharp:
|
||||
name: Analyze (csharp)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
shell: pwsh
|
||||
run: |
|
||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
languages: csharp
|
||||
build-mode: manual
|
||||
queries: security-extended
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
category: /language:csharp
|
||||
|
||||
analyze-actions:
|
||||
name: Analyze (actions)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
languages: actions
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
category: /language:actions
|
||||
@@ -0,0 +1,226 @@
|
||||
name: Forge Announce
|
||||
|
||||
# Triggered when a vX.Y.Z tag is pushed. Reads .github/forge-posts/<tag>.md
|
||||
# (Frontmatter + DE bullet body) and the matching English block from
|
||||
# HellionChat/HellionChat.yaml, builds a Discord-Webhook embed and posts
|
||||
# it to the Hellion Forge #changelog channel.
|
||||
#
|
||||
# Decoupled from release.yml: a fail here does not block the GitHub
|
||||
# release, and a fail there does not block the announce. Spec lives in
|
||||
# the Vault under "Hellion Chat Forge-Auto-Announce Spec".
|
||||
#
|
||||
# Security: the only user-controlled inputs that enter run-steps are the
|
||||
# tag name and the frontmatter values from a repo-internal markdown file.
|
||||
# Tag name is read via env: (TAG_NAME, $env:TAG_NAME) and validated against
|
||||
# ^v\d+\.\d+\.\d+$ before any string interpolation. Frontmatter values are
|
||||
# parsed by regex with explicit length caps. No webhook event payload data
|
||||
# (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
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
announce:
|
||||
name: Post changelog to Hellion Forge
|
||||
runs-on: ubuntu-latest
|
||||
# The DISCORD_FORGE_WEBHOOK secret lives under Settings → Environments
|
||||
# → Webhook (case-sensitive). Without this declaration the secret is
|
||||
# not in scope for the job.
|
||||
environment: Webhook
|
||||
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 }}
|
||||
|
||||
# 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(2) } else { $_ }
|
||||
}) -join "`n"
|
||||
|
||||
$header = "**Hellion Chat $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**Hellion Chat ", 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()
|
||||
}
|
||||
|
||||
# ---------- Char-Cap-Check (5500 Total auf title + description + footer) ----------
|
||||
$title = "Hellion Chat $version — $subtitle"
|
||||
$description = "**Deutsch**`n`n$deBody`n`n**English**`n`n$enBlock"
|
||||
$footerText = "Hellion Forge · $versionsnatur"
|
||||
$totalChars = $title.Length + $description.Length + $footerText.Length
|
||||
if ($totalChars -gt 5500) {
|
||||
throw "V6: Total char count $totalChars exceeds 5500 limit. Major-Release detected — please post manually via Bot/Multi-Embed (see forge style §8). Forge-Auto-Announce stays off for this tag."
|
||||
}
|
||||
Write-Host "Char-Cap OK: $totalChars / 5500"
|
||||
|
||||
# ---------- Embed-Payload bauen ----------
|
||||
$payload = [ordered]@{
|
||||
username = "Forge Herald"
|
||||
avatar_url = "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png"
|
||||
content = "<@&1500489631555260446>"
|
||||
allowed_mentions = [ordered]@{
|
||||
parse = @()
|
||||
roles = @("1500489631555260446")
|
||||
}
|
||||
embeds = @(
|
||||
[ordered]@{
|
||||
title = $title
|
||||
url = "https://github.com/JonKazama-Hellion/HellionChat/releases/tag/$tag"
|
||||
color = 12730636
|
||||
description = $description
|
||||
footer = [ordered]@{ text = $footerText }
|
||||
timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
name: Release
|
||||
|
||||
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
||||
# current Dalamud staging branch, locates the latest.zip produced by
|
||||
# DalamudPackager and attaches it to the matching GitHub Release.
|
||||
#
|
||||
# User-controlled inputs touched by this workflow:
|
||||
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
||||
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
||||
# All other values are either repo-controlled (paths under
|
||||
# HellionChat/bin/Release derived from Get-ChildItem) or pinned URLs to
|
||||
# goatcorp / GitHub. Nothing from a webhook event payload (issue/PR
|
||||
# titles, commit messages, etc.) flows into a run-step.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
# Manual recovery trigger. Use when a tag was pushed but the auto-run
|
||||
# was missed or failed: `gh workflow run release.yml -f tag=v0.6.1`.
|
||||
# The tag input is validated against the same semver regex as the
|
||||
# auto-trigger before any string interpolation happens.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Existing tag to (re)release, e.g. v0.6.1'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build and attach release ZIP
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
# On push:tags, github.ref_name is the tag — checkout default works.
|
||||
# On workflow_dispatch, ref defaults to the branch the action was
|
||||
# invoked from; we need to explicitly check out the tag the user
|
||||
# supplied so the build comes from the tagged commit, not main.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
- name: Download Dalamud staging
|
||||
shell: pwsh
|
||||
run: |
|
||||
$hooks = Join-Path $env:APPDATA "XIVLauncher\addon\Hooks\dev"
|
||||
New-Item -ItemType Directory -Force -Path $hooks | Out-Null
|
||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile dalamud.zip
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||
|
||||
- name: Locate latest.zip
|
||||
id: locate
|
||||
shell: pwsh
|
||||
run: |
|
||||
$zip = Get-ChildItem -Path HellionChat\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
|
||||
if (-not $zip)
|
||||
{
|
||||
throw "latest.zip not found under HellionChat\bin\Release"
|
||||
}
|
||||
Write-Host "Found: $($zip.FullName)"
|
||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
# 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:
|
||||
# workflow_dispatch carries the user-supplied tag in inputs.tag;
|
||||
# push:tags carries it in github.ref_name. Either way the value
|
||||
# is treated as a PowerShell variable (env-var pass), not as
|
||||
# inline shell text, and validated against the semver regex
|
||||
# below before any string interpolation.
|
||||
TAG_NAME: ${{ github.event.inputs.tag || 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
|
||||
|
||||
$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
|
||||
# 2-space yaml indent from each line.
|
||||
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||
if ($_ -match '^ ') { $_.Substring(2) } else { $_ }
|
||||
}) -join "`n"
|
||||
|
||||
$header = "**Hellion Chat $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**Hellion Chat ", 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()
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
Write-Host "Generated release body for $tag :"
|
||||
Write-Host "----------------------------------------"
|
||||
Write-Host $body
|
||||
Write-Host "----------------------------------------"
|
||||
|
||||
- name: Attach to GitHub release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
# Explicit tag_name so the action targets the correct release in
|
||||
# both push:tags (auto) and workflow_dispatch (manual recovery)
|
||||
# modes. Without this, dispatch runs would default to the branch
|
||||
# ref (main) and fail to find the release.
|
||||
tag_name: ${{ github.event.inputs.tag || github.ref_name }}
|
||||
files: ${{ steps.locate.outputs.path }}
|
||||
body_path: release-body.md
|
||||
fail_on_unmatched_files: true
|
||||
generate_release_notes: false
|
||||
@@ -11,6 +11,10 @@
|
||||
.vscode/
|
||||
scripts/
|
||||
|
||||
# Local test project (stays out of the published plugin repo;
|
||||
# pure-function safety net for refactor cycles)
|
||||
HellionChat.Tests/
|
||||
|
||||
# Packaging
|
||||
pack/
|
||||
|
||||
@@ -374,6 +378,9 @@ FodyWeavers.xsd
|
||||
|
||||
#Specs und Plan datein
|
||||
/.superpowers/
|
||||
|
||||
#Test Datein
|
||||
ChatTwo.Tests
|
||||
TestResults
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# AI assistance disclosure
|
||||
|
||||
Per the [Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/),
|
||||
this fork uses AI assistance at the **Pair** level. Pair means the maintainer
|
||||
plans the architecture, decides what gets built, reviews each change and
|
||||
tests against the running game; Claude (Anthropic) helps explain Dalamud
|
||||
APIs, suggests patterns, drafts code on request, and reviews approaches.
|
||||
Neither side acts autonomously: nothing ships without the maintainer's
|
||||
review, and Claude can't run the game.
|
||||
|
||||
The level varies by area and over time. Some commits are mostly hand-written
|
||||
with the AI used as a sounding board, others lean more on Claude for an API
|
||||
walkthrough or a code draft that the maintainer then reads, edits and
|
||||
integrates. The maintainer's commitment is to be able to explain why every
|
||||
piece of Hellion code is the way it is — not "I typed every character."
|
||||
|
||||
## What's where
|
||||
|
||||
Upstream Chat 2 (by Infi & Anna, EUPL-1.2) is the foundation and was not
|
||||
produced with AI assistance. Hellion-specific code lives in
|
||||
`ChatTwo/Privacy/`, `ChatTwo/Export/`, `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; the share of human vs. AI authorship varies file by file
|
||||
and is expected to keep shifting toward more hand-written work as the
|
||||
maintainer's plugin-dev experience grows.
|
||||
|
||||
## What AI is not used for
|
||||
|
||||
- **Visual assets.** Logos, icons, banners, screenshots are human-drawn or
|
||||
taken from the running game.
|
||||
- **German translations.** Written by the maintainer (native speaker).
|
||||
|
||||
## Tooling
|
||||
|
||||
- Claude (Anthropic) via Claude Code CLI as the main pair partner.
|
||||
- Context7 / Microsoft Learn for current Dalamud and .NET documentation.
|
||||
|
||||
## Contact
|
||||
|
||||
Questions about this disclosure: <https://github.com/JonKazama-Hellion/HellionChat/issues>.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Code of Conduct
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Our Pledge
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
3. Respecting **different viewpoints** and experiences.
|
||||
4. **Taking responsibility** for our actions and contributions.
|
||||
5. Gracefully giving and accepting **constructive feedback**.
|
||||
6. Committing to **repairing harm** when it occurs.
|
||||
7. Behaving in other ways that promote and sustain the **well-being of
|
||||
our community**.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Reporting
|
||||
|
||||
If something here is being broken, contact me directly. Do not open a
|
||||
public issue.
|
||||
|
||||
| Channel | Address |
|
||||
| ---------- | ------------------------ |
|
||||
| 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.
|
||||
|
||||
## Enforcement
|
||||
|
||||
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.
|
||||
3. Edit or removal of the offending content.
|
||||
4. Private written warning with a cooldown period.
|
||||
5. Temporary block from the repository or related spaces.
|
||||
6. Permanent block.
|
||||
|
||||
Severe cases skip the lower steps. I will not negotiate over harassment
|
||||
or threats.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
[https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/).
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
# 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.
|
||||
|
||||
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). Cherry-picks
|
||||
from upstream Chat 2 are selective and deliberate; not everything
|
||||
that lands there belongs here.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## 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. They make
|
||||
selective upstream cherry-picks much harder and the maintenance cost
|
||||
outweighs the benefit for a one-person project.
|
||||
- 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.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
## 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)
|
||||
- VS Code with the C# Dev Kit, Rider, or Visual Studio
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
For a smoke test in-game: build, copy the output into your Dalamud
|
||||
`devPlugins/HellionChat/` directory and load it via `/xlplugins`.
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
Every push and every pull request runs:
|
||||
|
||||
| Workflow | What it checks |
|
||||
| ------------- | ------------------------------------- |
|
||||
| `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.
|
||||
|
||||
## 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 owned by the upstream project
|
||||
and synced in via cherry-pick. Please contribute those to
|
||||
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) instead.
|
||||
|
||||
## 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.
|
||||
|
||||
There is no separate CLA. Forking HellionChat is explicitly permitted
|
||||
under the EUPL-1.2, as with any EUPL-licensed project.
|
||||
|
||||
## Response Times
|
||||
|
||||
| Channel | Address |
|
||||
| ------------- | -------------------------- |
|
||||
| GitHub Issues | Preferred for bugs and feature requests |
|
||||
| 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.
|
||||
@@ -0,0 +1,51 @@
|
||||
HellionChat — a privacy-focused fork of ChatTwo for FINAL FANTASY XIV
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
Source code
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Copyright (c) 2022-2026 Infiziert90 (Infi) and Anna Clemens (ascclemens)
|
||||
Original ChatTwo authors and copyright holders of the upstream
|
||||
plugin this fork is built on. Their work covers the message store,
|
||||
the channel filtering, the sidebar tab system, the FFXIV chat
|
||||
hooks, the localisation infrastructure and most of the
|
||||
architecture HellionChat still relies on.
|
||||
|
||||
Copyright (c) 2025-2026 Florian Wathling / Hellion Online Media
|
||||
HellionChat-specific modifications, including the privacy filter,
|
||||
per-channel retention sweep, export pipeline, Auto-Tell-Tabs,
|
||||
German localisation and the EUPL-1.2 fork maintenance.
|
||||
|
||||
Source code is licensed under the European Union Public Licence
|
||||
(EUPL), Version 1.2 only. The full Licence text lives in the LICENSE
|
||||
file at the root of this repository. The official Licence website is
|
||||
at: https://eupl.eu/1.2/en/
|
||||
|
||||
This Work is provided "AS IS" without warranties of any kind. See
|
||||
Article 7 (Disclaimer of Warranty) and Article 8 (Disclaimer of
|
||||
Liability) of the Licence for the legally binding wording.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
Visual assets
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
Copyright (c) 2026 Florian Eck
|
||||
|
||||
Designer of the Hellion Forge logo and Hellion Online Media logo
|
||||
(variants located in docs/images and HellionChat/images).
|
||||
Exclusive usage and marketing rights licensed to Hellion Online
|
||||
Media. These assets are NOT covered by the EUPL-1.2 source code
|
||||
licence above and may not be reused, modified, or redistributed
|
||||
without separate permission from the copyright holder.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
Bundled assets
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Exo 2 font (HellionChat/Resources/HellionFont.ttf)
|
||||
SIL Open Font License 1.1, full text in HellionFont-OFL.txt.
|
||||
Bundled with permission per the OFL terms.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
Acknowledgements directed at the upstream ChatTwo authors live in
|
||||
NOTICE.md. The manual upstream-sync workflow lives in UPSTREAM_SYNC.md.
|
||||
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ChatTwo.Tests;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs history-preload coverage.
|
||||
//
|
||||
// These tests exercise MessageStore.GetTellHistoryWithSender, the query the
|
||||
// AutoTellTabsService uses to populate a freshly spawned temp tab with the
|
||||
// last conversations with that player.
|
||||
//
|
||||
// NOTE: like the rest of ChatTwo.Tests today, these will fail at runtime
|
||||
// until the project's Dalamud.dll runtime dependency is sorted out (see
|
||||
// Phase-2 backlog item "Test-Projekt fixen"). Compile-time the suite builds
|
||||
// fine via DALAMUD_HOME, so the tests guard against API drift even before
|
||||
// they can be executed locally.
|
||||
[TestClass]
|
||||
[TestSubject(typeof(MessageStore))]
|
||||
public class AutoTellTabsHistoryTest
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_FiltersByNameAndWorld()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99001;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Two tells with the target sender, one with a different sender on
|
||||
// the same world, one with the same name on a different world. Only
|
||||
// the first two should make it into the result.
|
||||
var asukaLichIn = TellMessage("Asuka", 76, receiver, now.AddMinutes(-30), ChatType.TellIncoming);
|
||||
var asukaLichOut = TellMessage("Asuka", 76, receiver, now.AddMinutes(-20), ChatType.TellOutgoing);
|
||||
var broboLich = TellMessage("Brobo", 76, receiver, now.AddMinutes(-10), ChatType.TellIncoming);
|
||||
var asukaOmega = TellMessage("Asuka", 90, receiver, now.AddMinutes(-5), ChatType.TellIncoming);
|
||||
|
||||
store.UpsertMessage(asukaLichIn);
|
||||
store.UpsertMessage(asukaLichOut);
|
||||
store.UpsertMessage(broboLich);
|
||||
store.UpsertMessage(asukaOmega);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 50);
|
||||
|
||||
Assert.AreEqual(2, result.Count);
|
||||
// Result is oldest-first so a tab can append messages chronologically.
|
||||
Assert.AreEqual(asukaLichIn.Id, result[0].Id);
|
||||
Assert.AreEqual(asukaLichOut.Id, result[1].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_RespectsLimit()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99002;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
var msg = TellMessage("Asuka", 76, receiver, now.AddMinutes(-i - 1), ChatType.TellIncoming);
|
||||
store.UpsertMessage(msg);
|
||||
}
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 5);
|
||||
|
||||
Assert.AreEqual(5, result.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_ZeroLimitReturnsEmpty()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99003;
|
||||
|
||||
var msg = TellMessage("Asuka", 76, receiver, DateTimeOffset.UtcNow, ChatType.TellIncoming);
|
||||
store.UpsertMessage(msg);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 0);
|
||||
|
||||
Assert.AreEqual(0, result.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_IgnoresOtherReceivers()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong ourReceiver = 99004;
|
||||
const ulong otherReceiver = 99005;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Tell on the local player's account.
|
||||
var ours = TellMessage("Asuka", 76, ourReceiver, now.AddMinutes(-1), ChatType.TellIncoming);
|
||||
// Same sender, but logged against a different local character —
|
||||
// common when the user has alts. Must not surface.
|
||||
var foreign = TellMessage("Asuka", 76, otherReceiver, now, ChatType.TellIncoming);
|
||||
|
||||
store.UpsertMessage(ours);
|
||||
store.UpsertMessage(foreign);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(ourReceiver, "Asuka", 76, limit: 50);
|
||||
|
||||
Assert.AreEqual(1, result.Count);
|
||||
Assert.AreEqual(ours.Id, result[0].Id);
|
||||
}
|
||||
|
||||
private static Message TellMessage(
|
||||
string senderName,
|
||||
uint senderWorld,
|
||||
ulong receiver,
|
||||
DateTimeOffset dateTime,
|
||||
ChatType chatType)
|
||||
{
|
||||
var senderSeString = new SeStringBuilder()
|
||||
.Add(new PlayerPayload(senderName, senderWorld))
|
||||
.AddText(senderName)
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.Build();
|
||||
|
||||
var contentSeString = new SeStringBuilder()
|
||||
.AddText("test message")
|
||||
.Build();
|
||||
|
||||
var senderChunks = ChunkUtil.ToChunks(senderSeString, ChunkSource.Sender, chatType).ToList();
|
||||
var contentChunks = ChunkUtil.ToChunks(contentSeString, ChunkSource.Content, chatType).ToList();
|
||||
|
||||
var chatCode = new ChatCode((XivChatType)chatType, XivChatRelationKind.LocalPlayer, XivChatRelationKind.LocalPlayer);
|
||||
return new Message(
|
||||
Guid.NewGuid(),
|
||||
receiver,
|
||||
0,
|
||||
dateTime,
|
||||
chatCode,
|
||||
senderChunks,
|
||||
contentChunks,
|
||||
senderSeString,
|
||||
contentSeString,
|
||||
Guid.Empty);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0-windows</TargetFrameworks>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="morelinq" Version="4.4.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ChatTwo\ChatTwo.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(IsCI)' == 'true'">
|
||||
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(DalamudLibPath)\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@@ -1,293 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Chat2PartyFinderPayload = ChatTwo.Util.PartyFinderPayload;
|
||||
|
||||
namespace ChatTwo.Tests;
|
||||
|
||||
[TestClass]
|
||||
[TestSubject(typeof(MessageStore))]
|
||||
public class MessageStoreTest {
|
||||
// From Message.cs
|
||||
private static readonly byte[] ExtraChatChannelPayloadBytes = [0, 0x27, 18, 0x20];
|
||||
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
public static string GetImportPath() {
|
||||
string[] importPaths = [
|
||||
@".\TestData",
|
||||
@"..\TestData",
|
||||
@"..\..\TestData",
|
||||
@"..\..\..\TestData",
|
||||
];
|
||||
var importPath = importPaths.FirstOrDefault(Directory.Exists);
|
||||
if (string.IsNullOrEmpty(importPath)) {
|
||||
throw new DirectoryNotFoundException("Could not find the import path");
|
||||
}
|
||||
return importPath;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void StoreAndRetrieve() {
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
// Write the message.
|
||||
var input = BigMessage();
|
||||
store.UpsertMessage(input);
|
||||
|
||||
// Read the message back.
|
||||
using var messageEnumerator = store.GetMostRecentMessages();
|
||||
var messages = messageEnumerator.ToList();
|
||||
Assert.AreEqual(1, messages.Count);
|
||||
AssertMessagesEqual(input, messages.First());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void RetrieveMultiple() {
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
// Insert 10 messages in the wrong order of date.
|
||||
var messages = new List<Message>();
|
||||
const uint receiver = 12345;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var message = BigMessage(true, receiver, now.AddSeconds(-i));
|
||||
TestContext.WriteLine($"Inserting message {i}: {message.Id}");
|
||||
store.UpsertMessage(message);
|
||||
messages.Add(message);
|
||||
}
|
||||
|
||||
// Insert a message for a different receiver. This shouldn't be returned
|
||||
// because of the receiver filtering.
|
||||
var otherReceiverMsg = BigMessage(receiver: receiver + 1, dateTime: now.AddSeconds(1));
|
||||
TestContext.WriteLine($"Inserting other receiver message: {otherReceiverMsg.Id}");
|
||||
store.UpsertMessage(otherReceiverMsg);
|
||||
|
||||
// Query the most recent 5 messages. Should return the 4 newest messages
|
||||
// from the list, as well as the different receiver message because we
|
||||
// aren't filtering.
|
||||
using var unfilteredMessageEnumerator = store.GetMostRecentMessages(count: 5);
|
||||
var outputMessages = unfilteredMessageEnumerator.ToList();
|
||||
var gotIds = outputMessages.Select(m => m.Id).ToList();
|
||||
TestContext.WriteLine($"Query 1 got IDs: {string.Join(", ", gotIds)}");
|
||||
AssertGuidsEqual(new List<Guid> {
|
||||
messages[3].Id,
|
||||
messages[2].Id,
|
||||
messages[1].Id,
|
||||
messages[0].Id,
|
||||
otherReceiverMsg.Id
|
||||
}, gotIds);
|
||||
|
||||
// Query the most recent 5 messages but filter by receiver ID.
|
||||
using var filteredByReceiverMessageEnumerator = store.GetMostRecentMessages(receiver: receiver, count: 5);
|
||||
outputMessages = filteredByReceiverMessageEnumerator.ToList();
|
||||
gotIds = outputMessages.Select(m => m.Id).ToList();
|
||||
TestContext.WriteLine($"Query 2 got IDs: {string.Join(", ", gotIds)}");
|
||||
AssertGuidsEqual(new List<Guid> {
|
||||
messages[4].Id,
|
||||
messages[3].Id,
|
||||
messages[2].Id,
|
||||
messages[1].Id,
|
||||
messages[0].Id,
|
||||
}, gotIds);
|
||||
|
||||
// Query the most recent 5 messages but only since a specific date.
|
||||
using var filteredByReceiverAndDateMessageEnumerator = store.GetMostRecentMessages(receiver, since: messages[1].Date, count: 5);
|
||||
outputMessages = filteredByReceiverAndDateMessageEnumerator.ToList();
|
||||
gotIds = outputMessages.Select(m => m.Id).ToList();
|
||||
TestContext.WriteLine($"Query 3 got IDs: {string.Join(", ", gotIds)}");
|
||||
AssertGuidsEqual(new List<Guid> {
|
||||
messages[1].Id,
|
||||
messages[0].Id,
|
||||
}, gotIds);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
// This test guards against the data format changing in an incompatible way.
|
||||
public void RetrieveExisting() {
|
||||
var input = BigMessage(uniqId: false);
|
||||
|
||||
var dbPath = Path.Join(GetImportPath(), "existing.db");
|
||||
TestContext.WriteLine($"Using existing database: {dbPath}");
|
||||
Assert.IsTrue(File.Exists(dbPath));
|
||||
|
||||
// Uncomment this section to regenerate the existing database.
|
||||
/*
|
||||
File.Delete(dbPath);
|
||||
using (var newStore = new MessageStore(dbPath)) {
|
||||
newStore.UpsertMessage(input);
|
||||
}
|
||||
*/
|
||||
|
||||
using var store = new MessageStore(dbPath);
|
||||
using var existingMessageEnumerator = store.GetMostRecentMessages();
|
||||
var output = existingMessageEnumerator.ToList();
|
||||
Assert.AreEqual(1, output.Count);
|
||||
AssertMessagesEqual(input, output[0]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(30_000)]
|
||||
public void ProfileMany() {
|
||||
const int count = 20_000;
|
||||
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
var message = BigMessage(uniqId: true);
|
||||
store.UpsertMessage(message);
|
||||
}
|
||||
|
||||
using var messageEnumerator = store.GetMostRecentMessages(count: count);
|
||||
var messages = messageEnumerator.ToList();
|
||||
Assert.AreEqual(count, messages.Count);
|
||||
foreach (var message in messages) {
|
||||
// Load the message because they are lazily parsed.
|
||||
Assert.IsTrue(message.Id != Guid.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Message BigMessage(bool uniqId = true, uint receiver = 12345, DateTimeOffset? dateTime = null) {
|
||||
// NOTE: These values aren't valid in the game.
|
||||
// NOTE: we can't test UiForeground, UiGlow, or AutoTranslatePayload
|
||||
// because they load data from the game.
|
||||
var senderSeString = new SeStringBuilder()
|
||||
.AddText("<")
|
||||
.Add(new PlayerPayload("Player Name", 12345))
|
||||
.AddItalics("Player Name")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.AddText(">: ")
|
||||
.Build();
|
||||
var extraChatId = Guid.Parse("03d9e6d4-dc1a-4005-bbe7-66b8c3529277");
|
||||
var contentSeString = new SeStringBuilder()
|
||||
.Add(new RawPayload(ExtraChatChannelPayloadBytes.Concat(extraChatId.ToByteArray()).ToArray()))
|
||||
.AddIcon(BitmapFontIcon.IslandSanctuary)
|
||||
.AddMapLink(1, 2, 3, 4)
|
||||
.AddText("map")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.AddQuestLink(12345)
|
||||
.AddText("quest")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.Add(new DalamudLinkPayload())
|
||||
.AddText("dalamud")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.AddStatusLink(12345)
|
||||
.AddText("status")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.AddPartyFinderLink(12345)
|
||||
.AddText("party finder")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.Build();
|
||||
|
||||
// Add Chat 2 specific payloads (that can't be serialized into the
|
||||
// SeString).
|
||||
var contentChunks = ChunkUtil.ToChunks(contentSeString, ChunkSource.Content, ChatType.Say).ToList();
|
||||
contentChunks = contentChunks.Concat([
|
||||
new TextChunk(ChunkSource.Content, new Chat2PartyFinderPayload(12345), "chat 2 party finder"),
|
||||
new TextChunk(ChunkSource.Content, new AchievementPayload(12345), "chat 2 achievement"),
|
||||
new TextChunk(ChunkSource.Content, new UriPayload(new Uri("https://dalamud.dev")), "chat 2 uri"),
|
||||
]).ToList();
|
||||
|
||||
var chatCode = new ChatCode((XivChatType)46, XivChatRelationKind.LocalPlayer, XivChatRelationKind.EngagedEnemy);
|
||||
return new Message(
|
||||
uniqId ? Guid.NewGuid() : Guid.Parse("f011343e-6a21-49e5-a6f9-238f0f1f8c2c"),
|
||||
receiver,
|
||||
54321,
|
||||
dateTime ?? DateTimeOffset.FromUnixTimeMilliseconds(1713520182440),
|
||||
chatCode,
|
||||
ChunkUtil.ToChunks(senderSeString, ChunkSource.Sender, ChatType.Debug).ToList(),
|
||||
contentChunks,
|
||||
senderSeString,
|
||||
contentSeString,
|
||||
extraChatId
|
||||
);
|
||||
}
|
||||
|
||||
internal static void AssertMessagesEqual(Message input, Message output) {
|
||||
// Check basic fields.
|
||||
Assert.AreEqual(input.Id, output.Id);
|
||||
Assert.AreEqual(input.Receiver, output.Receiver);
|
||||
Assert.AreEqual(input.ContentId, output.ContentId);
|
||||
// Assert time is within 1 second
|
||||
var timeDifference = Math.Abs(input.Date.ToUniversalTime().Subtract(output.Date.ToUniversalTime()).TotalSeconds);
|
||||
Assert.IsTrue(timeDifference < 1);
|
||||
Assert.AreEqual(input.Code, output.Code);
|
||||
Assert.AreEqual($"{input.SenderSource.Encode():X}", $"{output.SenderSource.Encode():X}");
|
||||
Assert.AreEqual($"{input.ContentSource.Encode():X}", $"{output.ContentSource.Encode():X}");
|
||||
Assert.AreEqual(input.SortCodeV2, output.SortCodeV2);
|
||||
Assert.AreEqual(input.ExtraChatChannel, output.ExtraChatChannel);
|
||||
|
||||
// Check chunks.
|
||||
AssertChunksEqual(input.Sender, output.Sender);
|
||||
AssertChunksEqual(input.Content, output.Content);
|
||||
}
|
||||
|
||||
private static void AssertChunksEqual(IReadOnlyList<Chunk> inputChunks, IReadOnlyList<Chunk> outputChunks) {
|
||||
Assert.AreEqual(inputChunks.Count, outputChunks.Count);
|
||||
for (var i = 0; i < inputChunks.Count; i++) {
|
||||
var inputChunk = inputChunks[i];
|
||||
var outputChunk = outputChunks[i];
|
||||
Assert.AreEqual(inputChunk.Source, outputChunk.Source);
|
||||
switch (inputChunk.Link) {
|
||||
case AchievementPayload inputAchievementPayload:
|
||||
Assert.AreEqual(inputAchievementPayload.Id, ((AchievementPayload) outputChunk.Link)!.Id);
|
||||
break;
|
||||
case Chat2PartyFinderPayload inputPartyFinderPayload:
|
||||
Assert.AreEqual(inputPartyFinderPayload.Id, ((Chat2PartyFinderPayload) outputChunk.Link)!.Id);
|
||||
break;
|
||||
case UriPayload inputUriPayload:
|
||||
Assert.AreEqual(inputUriPayload.Uri, ((UriPayload) outputChunk.Link)!.Uri);
|
||||
break;
|
||||
case null:
|
||||
Assert.IsTrue(outputChunk.Link == null);
|
||||
break;
|
||||
default:
|
||||
Assert.AreEqual($"{inputChunk.Link.Encode():X}", $"{outputChunk.Link!.Encode():X}");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (inputChunk) {
|
||||
case TextChunk inputTextChunk:
|
||||
var outputTextChunk = (TextChunk)outputChunk;
|
||||
Assert.AreEqual(inputTextChunk.FallbackColour, outputTextChunk.FallbackColour);
|
||||
Assert.AreEqual(inputTextChunk.Foreground, outputTextChunk.Foreground);
|
||||
Assert.AreEqual(inputTextChunk.Glow, outputTextChunk.Glow);
|
||||
Assert.AreEqual(inputTextChunk.Italic, outputTextChunk.Italic);
|
||||
Assert.AreEqual(inputTextChunk.Content, outputTextChunk.Content);
|
||||
break;
|
||||
case IconChunk inputIconChunk:
|
||||
Assert.AreEqual(inputIconChunk.Icon, ((IconChunk) outputChunk).Icon);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown chunk type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertGuidsEqual(IReadOnlyList<Guid> expected, IReadOnlyList<Guid> got) {
|
||||
Assert.AreEqual(expected.Count, got.Count);
|
||||
for (var i = 0; i < expected.Count; i++) {
|
||||
Assert.AreEqual(expected[i].ToString(), got[i].ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
-22
@@ -1,22 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatTwo", "ChatTwo\ChatTwo.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatTwo.Tests", "ChatTwo.Tests\ChatTwo.Tests.csproj", "{A9FE423A-240C-4EDA-ACC6-21474B562128}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9FE423A-240C-4EDA-ACC6-21474B562128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9FE423A-240C-4EDA-ACC6-21474B562128}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9FE423A-240C-4EDA-ACC6-21474B562128}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9FE423A-240C-4EDA-ACC6-21474B562128}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,75 +0,0 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<!-- Hellion Chat versioning runs separately from upstream Chat 2.
|
||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
||||
called out in the yaml changelog so users can see what it
|
||||
derives from. -->
|
||||
<Version>0.4.0</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
||||
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
||||
keeping our state independent from the upstream plugin.
|
||||
Code namespace stays ChatTwo.* so upstream cherry-picks
|
||||
apply cleanly. -->
|
||||
<AssemblyName>HellionChat</AssemblyName>
|
||||
<RootNamespace>ChatTwo</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
|
||||
<PackageReference Include="morelinq" Version="4.4.0" />
|
||||
<PackageReference Include="Pidgin" Version="3.3.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Language.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Language.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Language.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Language.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- HellionChat — Hellion-specific resource bundle (HellionStrings.resx
|
||||
+ HellionStrings.<lang>.resx) is picked up automatically by the SDK
|
||||
default include. Designer.cs is hand-maintained, no auto-gen needed. -->
|
||||
|
||||
<!-- Bundled Hellion font (Exo 2, OFL-1.1). Embedded as a manifest
|
||||
resource with a fixed LogicalName so FontManager can pull the
|
||||
bytes back at runtime via AddFontFromMemory. The OFL license
|
||||
text travels with it inside the assembly to satisfy the
|
||||
"license must be distributed with the font" clause. -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\HellionFont.ttf">
|
||||
<LogicalName>HellionFont.ttf</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\HellionFont-OFL.txt">
|
||||
<LogicalName>HellionFont-OFL.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="images\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Copy images/icon.png next to the built DLL so Dalamud's local
|
||||
plugin loader finds it at <plugindir>/images/icon.png. The
|
||||
DalamudPackager.targets file in this directory then includes
|
||||
the same path inside the release ZIP — see that file for the
|
||||
full packaging override. -->
|
||||
<ItemGroup>
|
||||
<None Include="images\icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
HellionChat — DalamudPackager override.
|
||||
|
||||
The default DalamudPackager.targets shipped by the SDK does not set
|
||||
HandleImages / ImagesPath, so the images/ directory is silently
|
||||
excluded from the release ZIP. The presence of this file at
|
||||
$(ProjectDir)DalamudPackager.targets disables the SDK's default
|
||||
target (it guards on `!Exists('$(PackagerTargetFile)')`) and lets
|
||||
us call the packager task ourselves with the image fields wired in.
|
||||
|
||||
Apart from HandleImages + ImagesPath the property list mirrors the
|
||||
SDK default verbatim so we don't lose any other manifest field as
|
||||
the upstream SDK evolves.
|
||||
-->
|
||||
<Project>
|
||||
<Target Name="HellionDalamudPackagerDebug"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(Configuration)' == 'Debug'">
|
||||
<DalamudPackager ProjectDir="$(ProjectDir)"
|
||||
OutputPath="$(OutputPath)"
|
||||
AssemblyName="$(AssemblyName)"
|
||||
MakeZip="false"
|
||||
Author="$(Author)"
|
||||
Name="$(Name)"
|
||||
MinimumDalamudVersion="$(MinimumDalamudVersion)"
|
||||
Punchline="$(Punchline)"
|
||||
Description="$(Description)"
|
||||
ApplicableVersion="$(ApplicableVersion)"
|
||||
RepoUrl="$(RepoUrl)"
|
||||
Tags="$(Tags)"
|
||||
CategoryTags="$(CategoryTags)"
|
||||
DalamudApiLevel="$(DalamudApiLevel)"
|
||||
LoadRequiredState="$(LoadRequiredState)"
|
||||
LoadSync="$(LoadSync)"
|
||||
CanUnloadAsync="$(CanUnloadAsync)"
|
||||
LoadPriority="$(LoadPriority)"
|
||||
ImageUrls="$(ImageUrls)"
|
||||
IconUrl="$(IconUrl)"
|
||||
Changelog="$(Changelog)"
|
||||
AcceptsFeedback="$(AcceptsFeedback)"
|
||||
FeedbackMessage="$(FeedbackMessage)"
|
||||
HandleImages="true"
|
||||
ImagesPath="$(ProjectDir)images" />
|
||||
</Target>
|
||||
|
||||
<Target Name="HellionDalamudPackagerRelease"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(Configuration)' == 'Release'">
|
||||
<DalamudPackager ProjectDir="$(ProjectDir)"
|
||||
OutputPath="$(OutputPath)"
|
||||
AssemblyName="$(AssemblyName)"
|
||||
MakeZip="true"
|
||||
Author="$(Author)"
|
||||
Name="$(Name)"
|
||||
MinimumDalamudVersion="$(MinimumDalamudVersion)"
|
||||
Punchline="$(Punchline)"
|
||||
Description="$(Description)"
|
||||
ApplicableVersion="$(ApplicableVersion)"
|
||||
RepoUrl="$(RepoUrl)"
|
||||
Tags="$(Tags)"
|
||||
CategoryTags="$(CategoryTags)"
|
||||
DalamudApiLevel="$(DalamudApiLevel)"
|
||||
LoadRequiredState="$(LoadRequiredState)"
|
||||
LoadSync="$(LoadSync)"
|
||||
CanUnloadAsync="$(CanUnloadAsync)"
|
||||
LoadPriority="$(LoadPriority)"
|
||||
ImageUrls="$(ImageUrls)"
|
||||
IconUrl="$(IconUrl)"
|
||||
Changelog="$(Changelog)"
|
||||
AcceptsFeedback="$(AcceptsFeedback)"
|
||||
FeedbackMessage="$(FeedbackMessage)"
|
||||
HandleImages="true"
|
||||
ImagesPath="$(ProjectDir)images" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,254 +0,0 @@
|
||||
name: Hellion Chat
|
||||
author: JonKazama-Hellion
|
||||
punchline: Chat 2 with privacy controls aligned to EU, US and JP rules
|
||||
description: |-
|
||||
Hellion Chat is built on top of Chat 2 with one removal and a stack
|
||||
of privacy controls on top. Tabs, channel filters, RGB colours,
|
||||
emotes, screenshot mode, IPC integration and the chat replacement
|
||||
window itself work the same. The optional webinterface that Chat 2
|
||||
ships is intentionally not part of this fork because it serves a
|
||||
different use case from the smaller default footprint Hellion Chat
|
||||
is built around.
|
||||
|
||||
On top of that, Hellion Chat adds privacy and data-handling controls
|
||||
designed to align with the modern data protection rules that apply
|
||||
across the EU, the United States and Japan. By default only your own
|
||||
conversations are stored; messages from strangers, NPCs and system
|
||||
spam stay out of the database. Retention windows are configurable per
|
||||
channel, history can be wiped retroactively, and stored data can be
|
||||
exported on demand.
|
||||
|
||||
Key additions on top of Chat 2:
|
||||
|
||||
- Channel whitelist with a Privacy-First default
|
||||
- Per-channel retention with a daily background sweep
|
||||
- Retroactive cleanup with a Ctrl+Shift confirm
|
||||
- Export to Markdown, JSON or CSV
|
||||
- First-run wizard with three preset profiles (Privacy-First, Casual,
|
||||
Full History)
|
||||
- Bilingual UI (English and German) with live language switching
|
||||
- Independent plugin state — own config file and database directory,
|
||||
so Hellion Chat does not share state with the upstream plugin
|
||||
|
||||
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
|
||||
repo_url: https://github.com/JonKazama-Hellion/HellionChat
|
||||
accepts_feedback: true
|
||||
tags:
|
||||
- Social
|
||||
- UI
|
||||
- Chat
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 0.4.0 — Auto-Tell-Tabs**
|
||||
|
||||
Auto-Tell-Tabs lets you turn each /tell into a session-only tab
|
||||
dedicated to that conversation partner. The original use case is
|
||||
the FFXIV club greeter who has to track 5–15 parallel "hi, welcome"
|
||||
exchanges; everyone else can disable the feature in one click and
|
||||
go back to a single Tell Exclusive tab.
|
||||
|
||||
What lands in this release:
|
||||
|
||||
- Auto-spawn temp tab "Name@World" on /tell (incoming and outgoing)
|
||||
- Tab limit (default 15, range 1–50) with LRU drop that prefers
|
||||
greeted tabs first, then sorts by last activity
|
||||
- History preload from the local message store (default 20 tells,
|
||||
range 0–100) with a "— Earlier conversations —" separator above
|
||||
the live tell that triggered the spawn
|
||||
- Optional "mark as greeted" toggle button (off by default,
|
||||
greeter-specific) that dims the tab name and lets you flip the
|
||||
status
|
||||
- Section header "Active Tells (n)" or compact-mode separator in
|
||||
the sidebar between persistent tabs and the temp tabs
|
||||
- Settings UI under Chat (toggle / limit / compact / greeted-toggle)
|
||||
and Privacy (history preload count), with hover-tooltip help
|
||||
markers replacing the previous wall-of-text descriptions for the
|
||||
new sections
|
||||
- Save and load filters strip temp tabs from the on-disk config so
|
||||
a crash or a sidebar-mode toggle never persists or wipes them
|
||||
|
||||
Compatibility note: if XIV Messanger or another plugin is
|
||||
suppressing direct messages, disable its "Suppress DMs" option so
|
||||
Hellion Chat can receive tells and open the auto tabs.
|
||||
|
||||
Configuration version bumps from 8 to 9. Existing users get a one-
|
||||
shot notification on the first start, defaults are seeded by
|
||||
property initializers, persistent tabs are untouched.
|
||||
|
||||
The vertical sidebar tab view becomes the default for fresh
|
||||
installs; existing users keep their saved preference.
|
||||
|
||||
Inspired by the per-sender tab pattern in XIV InstantMessenger
|
||||
(Limiana, AGPL-3.0). No code was ported across the licence
|
||||
boundary; only the architectural concept influenced this design.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.3.1 — Upstream emote regression fix**
|
||||
|
||||
Cherry-picks Infi's upstream commit ff899ff "Fix a regression
|
||||
from API 15 updates" which changes the BetterTTV emote DTOs
|
||||
(Emote and Top100) from public fields to public properties.
|
||||
System.Text.Json under the API 15 toolchain only honours the
|
||||
[JsonPropertyName] attribute on properties, so the previous
|
||||
field-based version deserialised every fetched emote into empty
|
||||
default values. Result: BetterTTV emotes were silently broken
|
||||
on fresh installs. The fix is six lines and applies cleanly on
|
||||
top of our defensive null-check from earlier; the EmoteCache
|
||||
path-traversal hardening from 0.3.0 stays as it is.
|
||||
|
||||
Authorship of the fix is preserved with git cherry-pick -x, so
|
||||
Infi shows up as the author on the commit. Thanks to him for
|
||||
catching it in the upstream codebase.
|
||||
|
||||
**Hellion Chat 0.3.0 — Audit hardening, brand sweep and rebrand of slash commands**
|
||||
|
||||
This release closes the remaining audit follow-ups from the
|
||||
0.2.0 cleanup and finishes turning Hellion Chat into a properly
|
||||
branded fork rather than a Chat 2 with a different name.
|
||||
|
||||
Slash commands have been renamed across the board so they no
|
||||
longer collide with the upstream plugin and tell you which
|
||||
plugin owns them at a glance:
|
||||
|
||||
- /chat2 becomes /hellion
|
||||
- /chat2Viewer becomes /hellionView
|
||||
- /clearlog2 becomes /clearhellion
|
||||
- /chat2Debugger becomes /hellionDebugger (internal)
|
||||
- /chat2SeString becomes /hellionSeString (internal)
|
||||
|
||||
This is a breaking change for anyone with macros bound to the
|
||||
old command names. The upstream Chat 2 commands keep working
|
||||
if you also have that plugin installed.
|
||||
|
||||
Privacy and storage hardening based on the post-0.2.0 audit:
|
||||
|
||||
- Privacy filter master switch now states explicitly that the
|
||||
filter only governs storage, not the live chat log
|
||||
- Emote cache refuses to write outside its own directory if a
|
||||
third-party API ever returns a path that escapes
|
||||
- Retention sweep is serialised so the 24h auto-sweep and the
|
||||
manual button cannot launch in parallel and race for the
|
||||
SQLite connection
|
||||
- DbViewer paging uses an int constant and the matching SQL
|
||||
parameter name (the upstream code passed a float and a name
|
||||
without the parameter prefix; both worked in practice but
|
||||
were inconsistent)
|
||||
|
||||
Visual identity now matches the Hellion Online Media website:
|
||||
|
||||
- Theme palette switched to Arctic Cyan plus Ember Orange,
|
||||
matching the website's BRANDING.md tokens
|
||||
- Active tabs and window title bars use a brand-color-dark teal
|
||||
variation as identity colour, replacing the previous slate
|
||||
violet that did not appear in the brand
|
||||
- Resize grips and scrollbar grabs picked up Ember Orange
|
||||
instead of industrial amber on hover and active states
|
||||
|
||||
About tab rewritten and properly localised:
|
||||
|
||||
- New "Why this fork exists" block sets out the mission in
|
||||
neutral terms, framing Chat 2's full-history default as the
|
||||
right one for most users while explaining the narrower
|
||||
default footprint this fork chose
|
||||
- All Hellion-specific About copy now lives in HellionStrings
|
||||
in EN and DE, so German users see the Hellion sections in
|
||||
German rather than the upstream English fallback
|
||||
- Webinterface absence is described as a focus mismatch
|
||||
(different use case, substantial rebuild) rather than as
|
||||
a security issue with the upstream code
|
||||
- Translator list at the bottom of the About tab is reachable
|
||||
again on smaller settings windows
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.2.0 — Webinterface removed**
|
||||
|
||||
The upstream webinterface has been removed in its entirety. It
|
||||
serves a different use case from the smaller default footprint
|
||||
this fork is built around, namely remote access to chat from a
|
||||
second device. Aligning it with the data minimisation defaults
|
||||
Hellion Chat ships with would have meant a substantial rebuild.
|
||||
Removing it was the cleaner path for this particular fork.
|
||||
|
||||
What changed in this release:
|
||||
|
||||
- Settings tab "Webinterface" is gone, the corresponding
|
||||
Configuration fields (WebinterfaceEnabled / AutoStart / Password /
|
||||
Port / AuthStore / MaxLinesToSend) are dropped and stale entries
|
||||
fall out of the JSON on the next save automatically
|
||||
- The whole ChatTwo/Http tree, the bundled Svelte frontend in
|
||||
websiteBuild.zip and the WebinterfaceUtil helper are deleted
|
||||
- Watson.Lite (the HTTP server) and Newtonsoft.Json (only used by
|
||||
the webinterface JSON wire format) are removed from the
|
||||
package references
|
||||
- DbViewer's "Chat2 JSON Export" button is dropped because it
|
||||
serialised the database into the webinterface message protocol;
|
||||
the Privacy tab's MessageExporter (Markdown, JSON, CSV with
|
||||
channel and date filters) covers the same ground without the
|
||||
proprietary shape
|
||||
- About tab notes the absence so users coming from Chat 2 do not
|
||||
look for it
|
||||
- Configuration version bumps from 7 to 8 with a one-shot
|
||||
notification (EN + DE)
|
||||
|
||||
No changes to the privacy filter, retention sweep, first-run wizard
|
||||
or export pipeline. Existing chat history is preserved.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.1.2 — About tab rebrand, DBViewer polish**
|
||||
|
||||
- About tab now shows Hellion-specific maintainer, license, EU/US/JP
|
||||
disclaimer and SQUARE ENIX disclaimer instead of the inherited
|
||||
Chat 2 contact info; original ChatTwo translator credits stay
|
||||
visible under a clearly labelled upstream tree node
|
||||
- Localization clarified: Hellion-specific German strings are
|
||||
maintained by the fork maintainer, the Crowdin contributor list
|
||||
only covers the inherited upstream strings
|
||||
- Cherry-picked DBViewer UI improvements from upstream Chat 2
|
||||
(auto-scroll-reset on page change, tooltips on date reset,
|
||||
folder export, page arrows, localized export-running messages)
|
||||
- README rewritten in the Hellion project style with a tech-stack
|
||||
table, architecture tree, database column list, install guide,
|
||||
upstream-sync workflow notes and project-status checklist
|
||||
|
||||
**Hellion Chat 0.1.1 — Packaging and migration fixes**
|
||||
|
||||
- Plugin icon now ships inside the bundle, so the Hellion logo
|
||||
renders locally in the Dalamud plugin list once installed (the
|
||||
previous release relied only on the remote IconUrl)
|
||||
- Plugin icon downsampled from 1024×1024 to 256×256 to match the
|
||||
rendered size; loads faster and caches better
|
||||
- Migration from upstream Chat 2 is more robust: each file move is
|
||||
wrapped individually, a locked SQLite database no longer aborts
|
||||
the rest of the migration, and a warning notification fires when
|
||||
any file is held open (with a hint to disable Chat 2 and restart
|
||||
the game)
|
||||
- README ships a step-by-step migration guide (fresh install versus
|
||||
coming from Chat 2) and a troubleshooting section with manual
|
||||
recovery commands for Linux and Windows
|
||||
|
||||
**Hellion Chat 0.1.0 — Initial fork release**
|
||||
|
||||
Privacy
|
||||
- Channel whitelist filter in MessageStore.UpsertMessage with a
|
||||
Privacy-First default (own conversations only)
|
||||
- Per-channel retention with a 24-hour idempotent background sweep
|
||||
- Retroactive cleanup with a Ctrl+Shift confirm and VACUUM
|
||||
- Export to Markdown / JSON / CSV via Dalamud's file dialog
|
||||
|
||||
Onboarding
|
||||
- First-run wizard with three profiles: Privacy-First / Casual /
|
||||
Full History
|
||||
- Configuration migration that seeds defaults on update
|
||||
- One-shot migration from upstream Chat 2's pluginConfigs layout
|
||||
- Migrate3 idempotency recovery for half-migrated databases
|
||||
|
||||
Look & feel
|
||||
- Localized UI (English and German) with live language switching
|
||||
- Industrial HUD theme with cyan-teal action accents, slate-violet
|
||||
tabs, amber active highlights and a window-opacity slider
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
@@ -1 +0,0 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ChatTwo.Tests")]
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Hand-maintained strongly-typed accessor for HellionStrings.resx.
|
||||
// Mirrors the layout of Language.Designer.cs so the same Plugin.cs
|
||||
// LanguageChanged handler can update Culture for both classes.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ChatTwo.Resources;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
|
||||
internal class HellionStrings
|
||||
{
|
||||
private static global::System.Resources.ResourceManager? resourceMan;
|
||||
private static global::System.Globalization.CultureInfo? resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal HellionStrings() { }
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (resourceMan is null)
|
||||
resourceMan = new global::System.Resources.ResourceManager("ChatTwo.Resources.HellionStrings", typeof(HellionStrings).Assembly);
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo? Culture
|
||||
{
|
||||
get => resourceCulture;
|
||||
set => resourceCulture = value;
|
||||
}
|
||||
|
||||
private static string Get(string key)
|
||||
=> ResourceManager.GetString(key, resourceCulture) ?? key;
|
||||
|
||||
internal static string Privacy_Tab_Title => Get(nameof(Privacy_Tab_Title));
|
||||
internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name));
|
||||
internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description));
|
||||
internal static string Privacy_FilterEnabled_StorageOnly_Help => Get(nameof(Privacy_FilterEnabled_StorageOnly_Help));
|
||||
internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help));
|
||||
internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst));
|
||||
internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll));
|
||||
internal static string Privacy_Preset_SelectAll => Get(nameof(Privacy_Preset_SelectAll));
|
||||
internal static string Privacy_Group_DirectMessages => Get(nameof(Privacy_Group_DirectMessages));
|
||||
internal static string Privacy_Group_PartyAlliance => Get(nameof(Privacy_Group_PartyAlliance));
|
||||
internal static string Privacy_Group_FreeCompany => Get(nameof(Privacy_Group_FreeCompany));
|
||||
internal static string Privacy_Group_Linkshells => Get(nameof(Privacy_Group_Linkshells));
|
||||
internal static string Privacy_Group_CrossLinkshells => Get(nameof(Privacy_Group_CrossLinkshells));
|
||||
internal static string Privacy_Group_ExtraChat => Get(nameof(Privacy_Group_ExtraChat));
|
||||
internal static string Privacy_Group_PublicChat => Get(nameof(Privacy_Group_PublicChat));
|
||||
internal static string Privacy_Group_SystemLogs => Get(nameof(Privacy_Group_SystemLogs));
|
||||
internal static string Privacy_PersistUnknown_Name => Get(nameof(Privacy_PersistUnknown_Name));
|
||||
internal static string Privacy_PersistUnknown_Description => Get(nameof(Privacy_PersistUnknown_Description));
|
||||
|
||||
internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading));
|
||||
internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro));
|
||||
internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote));
|
||||
internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview));
|
||||
internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview));
|
||||
internal static string Cleanup_TotalStored => Get(nameof(Cleanup_TotalStored));
|
||||
internal static string Cleanup_WillKeep => Get(nameof(Cleanup_WillKeep));
|
||||
internal static string Cleanup_WillDelete => Get(nameof(Cleanup_WillDelete));
|
||||
internal static string Cleanup_Breakdown => Get(nameof(Cleanup_Breakdown));
|
||||
internal static string Cleanup_Marker_Keep => Get(nameof(Cleanup_Marker_Keep));
|
||||
internal static string Cleanup_Marker_Delete => Get(nameof(Cleanup_Marker_Delete));
|
||||
internal static string Cleanup_Apply_Label => Get(nameof(Cleanup_Apply_Label));
|
||||
internal static string Cleanup_Apply_Tooltip => Get(nameof(Cleanup_Apply_Tooltip));
|
||||
internal static string Cleanup_Running => Get(nameof(Cleanup_Running));
|
||||
internal static string Cleanup_PreviewError => Get(nameof(Cleanup_PreviewError));
|
||||
internal static string Cleanup_Success => Get(nameof(Cleanup_Success));
|
||||
internal static string Cleanup_Error => Get(nameof(Cleanup_Error));
|
||||
|
||||
internal static string Retention_Heading => Get(nameof(Retention_Heading));
|
||||
internal static string Retention_Enabled_Name => Get(nameof(Retention_Enabled_Name));
|
||||
internal static string Retention_Enabled_Description => Get(nameof(Retention_Enabled_Description));
|
||||
internal static string Retention_Default_Label => Get(nameof(Retention_Default_Label));
|
||||
internal static string Retention_Default_Help => Get(nameof(Retention_Default_Help));
|
||||
internal static string Retention_Reset_Spec => Get(nameof(Retention_Reset_Spec));
|
||||
internal static string Retention_Clear_Overrides => Get(nameof(Retention_Clear_Overrides));
|
||||
internal static string Retention_Tree_Heading => Get(nameof(Retention_Tree_Heading));
|
||||
internal static string Retention_Tag_Override => Get(nameof(Retention_Tag_Override));
|
||||
internal static string Retention_Tag_Spec => Get(nameof(Retention_Tag_Spec));
|
||||
internal static string Retention_Tag_Global => Get(nameof(Retention_Tag_Global));
|
||||
internal static string Retention_Reset_Button => Get(nameof(Retention_Reset_Button));
|
||||
internal static string Retention_Apply_Label => Get(nameof(Retention_Apply_Label));
|
||||
internal static string Retention_Apply_Tooltip => Get(nameof(Retention_Apply_Tooltip));
|
||||
internal static string Retention_Running => Get(nameof(Retention_Running));
|
||||
internal static string Retention_LastRun_Never => Get(nameof(Retention_LastRun_Never));
|
||||
internal static string Retention_LastRun_At => Get(nameof(Retention_LastRun_At));
|
||||
internal static string Retention_Success => Get(nameof(Retention_Success));
|
||||
internal static string Retention_Error => Get(nameof(Retention_Error));
|
||||
|
||||
internal static string Migration_Notification_Title => Get(nameof(Migration_Notification_Title));
|
||||
internal static string Migration_Notification_Content => Get(nameof(Migration_Notification_Content));
|
||||
internal static string Migration_Webinterface_Removed_Title => Get(nameof(Migration_Webinterface_Removed_Title));
|
||||
internal static string Migration_Webinterface_Removed_Content => Get(nameof(Migration_Webinterface_Removed_Content));
|
||||
|
||||
internal static string Wizard_Title => Get(nameof(Wizard_Title));
|
||||
internal static string Wizard_Intro => Get(nameof(Wizard_Intro));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Heading => Get(nameof(Wizard_Profile_PrivacyFirst_Heading));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Description => Get(nameof(Wizard_Profile_PrivacyFirst_Description));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Apply => Get(nameof(Wizard_Profile_PrivacyFirst_Apply));
|
||||
internal static string Wizard_Profile_Casual_Heading => Get(nameof(Wizard_Profile_Casual_Heading));
|
||||
internal static string Wizard_Profile_Casual_Description => Get(nameof(Wizard_Profile_Casual_Description));
|
||||
internal static string Wizard_Profile_Casual_Apply => Get(nameof(Wizard_Profile_Casual_Apply));
|
||||
internal static string Wizard_Profile_FullHistory_Heading => Get(nameof(Wizard_Profile_FullHistory_Heading));
|
||||
internal static string Wizard_Profile_FullHistory_Description => Get(nameof(Wizard_Profile_FullHistory_Description));
|
||||
internal static string Wizard_Profile_FullHistory_GdprWarning => Get(nameof(Wizard_Profile_FullHistory_GdprWarning));
|
||||
internal static string Wizard_Profile_FullHistory_Apply => Get(nameof(Wizard_Profile_FullHistory_Apply));
|
||||
internal static string Wizard_Reopen_Button => Get(nameof(Wizard_Reopen_Button));
|
||||
|
||||
internal static string Export_Heading => Get(nameof(Export_Heading));
|
||||
internal static string Export_Help => Get(nameof(Export_Help));
|
||||
internal static string Export_Range_Label => Get(nameof(Export_Range_Label));
|
||||
internal static string Export_Sender_Label => Get(nameof(Export_Sender_Label));
|
||||
internal static string Export_Channels_Heading => Get(nameof(Export_Channels_Heading));
|
||||
internal static string Export_Channels_AllOff => Get(nameof(Export_Channels_AllOff));
|
||||
internal static string Export_Format_Label => Get(nameof(Export_Format_Label));
|
||||
internal static string Export_Format_Markdown => Get(nameof(Export_Format_Markdown));
|
||||
internal static string Export_Format_Json => Get(nameof(Export_Format_Json));
|
||||
internal static string Export_Format_Csv => Get(nameof(Export_Format_Csv));
|
||||
internal static string Export_Button => Get(nameof(Export_Button));
|
||||
internal static string Export_Dialog_Title => Get(nameof(Export_Dialog_Title));
|
||||
internal static string Export_Running => Get(nameof(Export_Running));
|
||||
internal static string Export_Success => Get(nameof(Export_Success));
|
||||
internal static string Export_Empty => Get(nameof(Export_Empty));
|
||||
internal static string Export_Error => Get(nameof(Export_Error));
|
||||
|
||||
internal static string Theme_Heading => Get(nameof(Theme_Heading));
|
||||
internal static string Theme_Enabled_Name => Get(nameof(Theme_Enabled_Name));
|
||||
internal static string Theme_Enabled_Description => Get(nameof(Theme_Enabled_Description));
|
||||
internal static string Theme_WindowOpacity_Label => Get(nameof(Theme_WindowOpacity_Label));
|
||||
internal static string Theme_WindowOpacity_Help => Get(nameof(Theme_WindowOpacity_Help));
|
||||
internal static string Theme_UseHellionFont_Name => Get(nameof(Theme_UseHellionFont_Name));
|
||||
internal static string Theme_UseHellionFont_Description => Get(nameof(Theme_UseHellionFont_Description));
|
||||
|
||||
internal static string About_Maintainer_Heading => Get(nameof(About_Maintainer_Heading));
|
||||
internal static string About_Maintainer_Body => Get(nameof(About_Maintainer_Body));
|
||||
internal static string About_Maintainer_Website_Label => Get(nameof(About_Maintainer_Website_Label));
|
||||
internal static string About_Mission_Heading => Get(nameof(About_Mission_Heading));
|
||||
internal static string About_Mission_P1 => Get(nameof(About_Mission_P1));
|
||||
internal static string About_Mission_P2 => Get(nameof(About_Mission_P2));
|
||||
internal static string About_Mission_P3 => Get(nameof(About_Mission_P3));
|
||||
internal static string About_BuiltOn_Heading => Get(nameof(About_BuiltOn_Heading));
|
||||
internal static string About_BuiltOn_P1 => Get(nameof(About_BuiltOn_P1));
|
||||
internal static string About_BuiltOn_P2 => Get(nameof(About_BuiltOn_P2));
|
||||
internal static string About_BuiltOn_Upstream_Label => Get(nameof(About_BuiltOn_Upstream_Label));
|
||||
internal static string About_License_Heading => Get(nameof(About_License_Heading));
|
||||
internal static string About_License_P1 => Get(nameof(About_License_P1));
|
||||
internal static string About_License_P2 => Get(nameof(About_License_P2));
|
||||
internal static string About_License_P3 => Get(nameof(About_License_P3));
|
||||
internal static string About_SE_Heading => Get(nameof(About_SE_Heading));
|
||||
internal static string About_SE_P1 => Get(nameof(About_SE_P1));
|
||||
internal static string About_SE_P2 => Get(nameof(About_SE_P2));
|
||||
internal static string About_Localization_Heading => Get(nameof(About_Localization_Heading));
|
||||
internal static string About_Localization_P1 => Get(nameof(About_Localization_P1));
|
||||
internal static string About_Localization_P2 => Get(nameof(About_Localization_P2));
|
||||
internal static string About_Translators_TreeNode => Get(nameof(About_Translators_TreeNode));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs runtime strings
|
||||
internal static string AutoTellTabs_Migration_Title => Get(nameof(AutoTellTabs_Migration_Title));
|
||||
internal static string AutoTellTabs_Migration_Content => Get(nameof(AutoTellTabs_Migration_Content));
|
||||
internal static string AutoTellTabs_SectionHeader => Get(nameof(AutoTellTabs_SectionHeader));
|
||||
internal static string AutoTellTabs_HistorySeparator => Get(nameof(AutoTellTabs_HistorySeparator));
|
||||
internal static string AutoTellTabs_HistoryLoadError => Get(nameof(AutoTellTabs_HistoryLoadError));
|
||||
internal static string AutoTellTabs_GreetedTooltip => Get(nameof(AutoTellTabs_GreetedTooltip));
|
||||
internal static string AutoTellTabs_UnGreetedTooltip => Get(nameof(AutoTellTabs_UnGreetedTooltip));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
||||
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Name => Get(nameof(ChatLog_AutoTellTabs_Enable_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Description => Get(nameof(ChatLog_AutoTellTabs_Enable_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Name => Get(nameof(ChatLog_AutoTellTabs_Limit_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Description => Get(nameof(ChatLog_AutoTellTabs_Limit_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Name => Get(nameof(ChatLog_AutoTellTabs_Compact_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Description => Get(nameof(ChatLog_AutoTellTabs_Compact_Description));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Name => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Name));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Description => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Description));
|
||||
internal static string ChatLog_AutoTellTabs_PreloadHint => Get(nameof(ChatLog_AutoTellTabs_PreloadHint));
|
||||
internal static string ChatLog_AutoTellTabs_ConflictHint => Get(nameof(ChatLog_AutoTellTabs_ConflictHint));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Privacy settings tab
|
||||
internal static string Privacy_AutoTellTabs_Section_Title => Get(nameof(Privacy_AutoTellTabs_Section_Title));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Name => Get(nameof(Privacy_AutoTellTabs_Preload_Name));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Description => Get(nameof(Privacy_AutoTellTabs_Preload_Description));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Hint => Get(nameof(Privacy_AutoTellTabs_Preload_Hint));
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
/// <summary>
|
||||
/// ImGui style override for Hellion Chat. Industrial HUD palette with three
|
||||
/// distinct accents — cyan-teal as the primary action color, industrial
|
||||
/// amber for active state highlights, slate-violet for title bars and
|
||||
/// active tabs — on a deep-slate frame background with steel borders.
|
||||
///
|
||||
/// Two entry points:
|
||||
/// Push — local color stack, scoped via using-block. Use inside
|
||||
/// Hellion-only surfaces (Privacy tab, first-run wizard).
|
||||
/// PushGlobal — full color + style variable stack. Pushed once per frame
|
||||
/// in Plugin.Draw so every Hellion-rendered window inherits
|
||||
/// the look. Cheap to pop because ImGui keeps its own stack.
|
||||
/// </summary>
|
||||
internal static class HellionStyle
|
||||
{
|
||||
// Encoded as 0xRRGGBBAA, matching ChatTwo convention (see Settings.cs
|
||||
// Ko-fi buttons). RgbaToAbgr handles the byte swap to the format ImGui
|
||||
// expects. Hex values are sourced from the Hellion Online Media brand
|
||||
// guide ("Arctic Cyan + Ember Glow", BRANDING.md in the website repo).
|
||||
|
||||
// Primary — Arctic Cyan, used for every interactive control (buttons,
|
||||
// checks, sliders, separators when hovered). Three brand stages plus a
|
||||
// hover that lifts to brand-color-light and a press that drops to
|
||||
// brand-color-dark.
|
||||
private const uint PrimaryRgba = 0x00BED2FF; // brand-color
|
||||
private const uint PrimaryHoverRgba = 0x4DD9E8FF; // brand-color-light
|
||||
private const uint PrimaryActiveRgba = 0x0097A7FF; // brand-color-dark
|
||||
|
||||
// Identity — brand-color-dark teal for window title bars and the
|
||||
// active tab. Sits visibly below the primary cyan on buttons so the
|
||||
// user sees "where am I" (deep teal) versus "what can I click"
|
||||
// (brand cyan) without leaving the cyan family.
|
||||
private const uint IdentityRgba = 0x0097A7FF; // brand-color-dark
|
||||
private const uint IdentityHoverRgba = 0x4DD9E8FF; // brand-color-light
|
||||
private const uint IdentityDeepRgba = 0x005670FF; // dimmer teal for unfocused-active tab
|
||||
|
||||
// Accent — Ember Orange for warm highlights on grips and scrollbar
|
||||
// pulls. Replaces the previous industrial amber so the plugin matches
|
||||
// the website's CTA palette. AccentActive is reserved for any future
|
||||
// pressed-state on accent surfaces; the current slots only need
|
||||
// AccentRgba and AccentHoverRgba.
|
||||
private const uint AccentRgba = 0xF97316FF; // accent-color
|
||||
private const uint AccentHoverRgba = 0xFB923CFF; // accent-color-light
|
||||
|
||||
// Surfaces — Hellion brand background ladder. Window darkest, frame
|
||||
// hover ladder climbs into surface tones. Matches the website's
|
||||
// background / background-medium / background-light / surface vars.
|
||||
private const uint WindowBgRgba = 0x070B12FF; // background
|
||||
private const uint ChildBgRgba = 0x0C1220FF; // background-medium
|
||||
private const uint PopupBgRgba = 0x0C1220FF; // background-medium
|
||||
private const uint FrameBgRgba = 0x141E30FF; // background-light
|
||||
private const uint FrameBgHoverRgba = 0x1A2538FF; // surface
|
||||
private const uint FrameBgActiveRgba = 0x22303FFF; // surface-hover
|
||||
// Cyan-tinted border — matches website --border-brand (cyan @ 40% α).
|
||||
private const uint BorderRgba = 0x00BED266;
|
||||
private const uint BorderShadowRgba = 0x00000000;
|
||||
|
||||
// Headers / collapsing-headers / tree nodes / selectables — same
|
||||
// surface ladder as frames so panels feel consistent.
|
||||
private const uint HeaderRgba = 0x141E30FF;
|
||||
private const uint HeaderHoverRgba = 0x1A2538FF;
|
||||
private const uint HeaderActiveRgba = 0x22303FFF;
|
||||
|
||||
// Title bars — Identity teal on active so the focused window reads
|
||||
// as "yours" without using accent or primary slots.
|
||||
private const uint TitleBgRgba = 0x070B12FF;
|
||||
private const uint TitleBgActiveRgba = IdentityRgba;
|
||||
private const uint TitleBgCollapsedRgba = 0x05080EFF;
|
||||
|
||||
// Tabs — neutral inactive, Identity-light on hover, Identity teal on
|
||||
// active. Unfocused-active uses the deeper Identity stage so an
|
||||
// unfocused window's active tab still reads but does not pull focus.
|
||||
private const uint TabRgba = 0x141E30FF;
|
||||
private const uint TabHoveredRgba = IdentityHoverRgba;
|
||||
private const uint TabActiveRgba = IdentityRgba;
|
||||
private const uint TabUnfocusedRgba = 0x0C1220FF;
|
||||
private const uint TabUnfocusedActiveRgba = IdentityDeepRgba;
|
||||
|
||||
// Scrollbar — Ember on grab so the pull stands out without competing
|
||||
// with the cyan action buttons. Idle grab is a subtle surface tone,
|
||||
// hover/active climb into accent.
|
||||
private const uint ScrollbarBgRgba = 0x070B12FF;
|
||||
private const uint ScrollbarGrabRgba = 0x22303FFF; // surface-hover
|
||||
private const uint ScrollbarGrabHoveredRgba = AccentHoverRgba;
|
||||
private const uint ScrollbarGrabActiveRgba = AccentRgba;
|
||||
|
||||
// Resize grip — same Ember treatment as the scrollbar.
|
||||
private const uint ResizeGripRgba = 0x141E30FF;
|
||||
private const uint ResizeGripHoveredRgba = AccentHoverRgba;
|
||||
private const uint ResizeGripActiveRgba = AccentRgba;
|
||||
|
||||
// Separator and check mark / slider follow the primary cyan.
|
||||
|
||||
/// <summary>
|
||||
/// Local color stack for Hellion-only surfaces. Cheap. Use inside a
|
||||
/// `using var _ = HellionStyle.Push();` block.
|
||||
/// </summary>
|
||||
internal static IDisposable Push()
|
||||
{
|
||||
var stack = new StackHandle();
|
||||
stack.PushColor(ImGuiCol.Button, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.ButtonHovered, PrimaryHoverRgba);
|
||||
stack.PushColor(ImGuiCol.ButtonActive, PrimaryActiveRgba);
|
||||
stack.PushColor(ImGuiCol.FrameBg, FrameBgRgba);
|
||||
stack.PushColor(ImGuiCol.FrameBgHovered, FrameBgHoverRgba);
|
||||
stack.PushColor(ImGuiCol.FrameBgActive, FrameBgActiveRgba);
|
||||
stack.PushColor(ImGuiCol.Border, BorderRgba);
|
||||
stack.PushColor(ImGuiCol.Header, HeaderRgba);
|
||||
stack.PushColor(ImGuiCol.HeaderHovered, HeaderHoverRgba);
|
||||
stack.PushColor(ImGuiCol.HeaderActive, HeaderActiveRgba);
|
||||
stack.PushColor(ImGuiCol.CheckMark, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.SliderGrab, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.SliderGrabActive, PrimaryHoverRgba);
|
||||
return stack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Global color and style-variable stack pushed once per frame in
|
||||
/// Plugin.Draw. Covers every ImGui surface the plugin renders so the
|
||||
/// Hellion look is consistent across upstream and Hellion tabs.
|
||||
/// </summary>
|
||||
/// <param name="windowOpacity">Window background alpha (0.5–1.0). Lower
|
||||
/// values let the game shine through the plugin panes.</param>
|
||||
internal static IDisposable PushGlobal(float windowOpacity = 1.0f)
|
||||
{
|
||||
var stack = new StackHandle();
|
||||
|
||||
// Mix the configured opacity into both the outer window and the
|
||||
// inner content child backgrounds — without ChildBg following the
|
||||
// slider the chat log stays opaque inside even when the user
|
||||
// wants to see the game behind it during combat. Form fields and
|
||||
// popups (FrameBg, PopupBg) still stay opaque so input is readable.
|
||||
var alphaByte = (uint)Math.Clamp((int)(windowOpacity * 255f), 0x55, 0xFF);
|
||||
var windowBgWithAlpha = (WindowBgRgba & 0xFFFFFF00u) | alphaByte;
|
||||
var childBgWithAlpha = (ChildBgRgba & 0xFFFFFF00u) | alphaByte;
|
||||
|
||||
// Layout — geometric edges, modest rounding, single-pixel borders.
|
||||
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, 4f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.ChildRounding, 3f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, 3f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.FrameRounding, 2f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.GrabRounding, 2f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.TabRounding, 2f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.ScrollbarRounding, 2f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 1f);
|
||||
stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
|
||||
|
||||
// Surfaces.
|
||||
stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha);
|
||||
stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha);
|
||||
stack.PushColor(ImGuiCol.PopupBg, PopupBgRgba);
|
||||
stack.PushColor(ImGuiCol.Border, BorderRgba);
|
||||
stack.PushColor(ImGuiCol.BorderShadow, BorderShadowRgba);
|
||||
|
||||
// Frames (input fields, combos, sliders).
|
||||
stack.PushColor(ImGuiCol.FrameBg, FrameBgRgba);
|
||||
stack.PushColor(ImGuiCol.FrameBgHovered, FrameBgHoverRgba);
|
||||
stack.PushColor(ImGuiCol.FrameBgActive, FrameBgActiveRgba);
|
||||
|
||||
// Title bars — tertiary identity on active.
|
||||
stack.PushColor(ImGuiCol.TitleBg, TitleBgRgba);
|
||||
stack.PushColor(ImGuiCol.TitleBgActive, TitleBgActiveRgba);
|
||||
stack.PushColor(ImGuiCol.TitleBgCollapsed, TitleBgCollapsedRgba);
|
||||
|
||||
// Buttons — primary cyan.
|
||||
stack.PushColor(ImGuiCol.Button, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.ButtonHovered, PrimaryHoverRgba);
|
||||
stack.PushColor(ImGuiCol.ButtonActive, PrimaryActiveRgba);
|
||||
|
||||
// Headers / selectables — slate with subtle steps.
|
||||
stack.PushColor(ImGuiCol.Header, HeaderRgba);
|
||||
stack.PushColor(ImGuiCol.HeaderHovered, HeaderHoverRgba);
|
||||
stack.PushColor(ImGuiCol.HeaderActive, HeaderActiveRgba);
|
||||
|
||||
// Tabs — tertiary identity for the active tab.
|
||||
stack.PushColor(ImGuiCol.Tab, TabRgba);
|
||||
stack.PushColor(ImGuiCol.TabHovered, TabHoveredRgba);
|
||||
stack.PushColor(ImGuiCol.TabActive, TabActiveRgba);
|
||||
stack.PushColor(ImGuiCol.TabUnfocused, TabUnfocusedRgba);
|
||||
stack.PushColor(ImGuiCol.TabUnfocusedActive, TabUnfocusedActiveRgba);
|
||||
|
||||
// Scrollbar.
|
||||
stack.PushColor(ImGuiCol.ScrollbarBg, ScrollbarBgRgba);
|
||||
stack.PushColor(ImGuiCol.ScrollbarGrab, ScrollbarGrabRgba);
|
||||
stack.PushColor(ImGuiCol.ScrollbarGrabHovered, ScrollbarGrabHoveredRgba);
|
||||
stack.PushColor(ImGuiCol.ScrollbarGrabActive, ScrollbarGrabActiveRgba);
|
||||
|
||||
// Resize grip — secondary amber on active.
|
||||
stack.PushColor(ImGuiCol.ResizeGrip, ResizeGripRgba);
|
||||
stack.PushColor(ImGuiCol.ResizeGripHovered, ResizeGripHoveredRgba);
|
||||
stack.PushColor(ImGuiCol.ResizeGripActive, ResizeGripActiveRgba);
|
||||
|
||||
// Check mark + slider grab — primary cyan.
|
||||
stack.PushColor(ImGuiCol.CheckMark, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.SliderGrab, PrimaryRgba);
|
||||
stack.PushColor(ImGuiCol.SliderGrabActive, PrimaryHoverRgba);
|
||||
|
||||
// Separator — primary cyan when hovered/active so the eye
|
||||
// immediately sees that splitters are interactive.
|
||||
stack.PushColor(ImGuiCol.Separator, BorderRgba);
|
||||
stack.PushColor(ImGuiCol.SeparatorHovered, PrimaryHoverRgba);
|
||||
stack.PushColor(ImGuiCol.SeparatorActive, PrimaryRgba);
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
||||
private sealed class StackHandle : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> _items = new(64);
|
||||
|
||||
internal void PushColor(ImGuiCol slot, uint rgba)
|
||||
=> _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
|
||||
|
||||
internal void PushStyleVar(ImGuiStyleVar var, float value)
|
||||
=> _items.Add(ImRaii.PushStyle(var, value));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (var i = _items.Count - 1; i >= 0; i--)
|
||||
_items[i].Dispose();
|
||||
_items.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
internal class Popout : Window
|
||||
{
|
||||
private readonly ChatLogWindow ChatLogWindow;
|
||||
private readonly Tab Tab;
|
||||
private readonly int Idx;
|
||||
|
||||
private long FrameTime; // set every frame
|
||||
private long LastActivityTime = Environment.TickCount64;
|
||||
|
||||
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
|
||||
{
|
||||
ChatLogWindow = chatLogWindow;
|
||||
Tab = tab;
|
||||
Idx = idx;
|
||||
|
||||
Size = new Vector2(350, 350);
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
||||
IsOpen = true;
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
}
|
||||
|
||||
public override void PreOpenCheck()
|
||||
{
|
||||
if (!Tab.PopOut)
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
public override bool DrawConditions()
|
||||
{
|
||||
FrameTime = Environment.TickCount64;
|
||||
if (Tab.IndependentHide ? HideStateCheck() : ChatLogWindow.IsHidden)
|
||||
return false;
|
||||
|
||||
if (!Plugin.Config.HideWhenInactive || (!Plugin.Config.InactivityHideActiveDuringBattle && Plugin.InBattle) || !Tab.UnhideOnActivity)
|
||||
{
|
||||
LastActivityTime = FrameTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Activity in the tab, this popout window, or the main chat log window.
|
||||
var lastActivityTime = Math.Max(Tab.LastActivity, LastActivityTime);
|
||||
lastActivityTime = Math.Max(lastActivityTime, ChatLogWindow.LastActivityTime);
|
||||
return FrameTime - lastActivityTime <= 1000 * Plugin.Config.InactivityHideTimeout;
|
||||
}
|
||||
|
||||
public override void PreDraw()
|
||||
{
|
||||
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
|
||||
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Push();
|
||||
|
||||
Flags = ImGuiWindowFlags.None;
|
||||
if (!Plugin.Config.ShowPopOutTitleBar)
|
||||
Flags |= ImGuiWindowFlags.NoTitleBar;
|
||||
|
||||
if (!Tab.CanMove)
|
||||
Flags |= ImGuiWindowFlags.NoMove;
|
||||
|
||||
if (!Tab.CanResize)
|
||||
Flags |= ImGuiWindowFlags.NoResize;
|
||||
|
||||
if (!ChatLogWindow.PopOutDocked[Idx])
|
||||
{
|
||||
var alpha = Tab.IndependentOpacity ? Tab.Opacity : Plugin.Config.WindowAlpha;
|
||||
BgAlpha = alpha / 100f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
using var id = ImRaii.PushId($"popout-{Tab.Identifier}");
|
||||
|
||||
if (!Plugin.Config.ShowPopOutTitleBar)
|
||||
{
|
||||
ImGui.TextUnformatted(Tab.Name);
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
var handler = ChatLogWindow.HandlerLender.Borrow();
|
||||
ChatLogWindow.DrawMessageLog(Tab, handler, ImGui.GetContentRegionAvail().Y, false);
|
||||
|
||||
if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows))
|
||||
LastActivityTime = FrameTime;
|
||||
}
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
||||
|
||||
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
|
||||
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop();
|
||||
}
|
||||
|
||||
public override void OnClose()
|
||||
{
|
||||
ChatLogWindow.PopOutWindows.Remove(Tab.Identifier);
|
||||
ChatLogWindow.Plugin.WindowSystem.RemoveWindow(this);
|
||||
|
||||
Tab.PopOut = false;
|
||||
ChatLogWindow.Plugin.SaveConfig();
|
||||
}
|
||||
|
||||
private enum HideState
|
||||
{
|
||||
None,
|
||||
Cutscene,
|
||||
CutsceneOverride,
|
||||
User,
|
||||
Battle
|
||||
}
|
||||
|
||||
private HideState CurrentHideState = HideState.None;
|
||||
|
||||
private bool HideStateCheck()
|
||||
{
|
||||
// if the chat has no hide state set, and the player has entered battle, we hide chat if they have configured it
|
||||
if (Tab.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
|
||||
CurrentHideState = HideState.Battle;
|
||||
|
||||
// If the chat is hidden because of battle, we reset it here
|
||||
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
||||
CurrentHideState = HideState.None;
|
||||
|
||||
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
|
||||
if (Tab.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive))
|
||||
{
|
||||
if (ChatLogWindow.Plugin.Functions.Chat.CheckHideFlags())
|
||||
CurrentHideState = HideState.Cutscene;
|
||||
}
|
||||
|
||||
// if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none
|
||||
if (CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride && !Plugin.CutsceneActive && !Plugin.GposeActive)
|
||||
CurrentHideState = HideState.None;
|
||||
|
||||
// if the chat is hidden because of a cutscene and the chat has been activated, show chat
|
||||
if (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate)
|
||||
CurrentHideState = HideState.CutsceneOverride;
|
||||
|
||||
// if the user hid the chat and is now activating chat, reset the hide state
|
||||
if (CurrentHideState == HideState.User && ChatLogWindow.Activate)
|
||||
CurrentHideState = HideState.None;
|
||||
|
||||
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui.SettingsTabs;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
public sealed class SettingsWindow : Window
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
|
||||
private Configuration Mutable { get; }
|
||||
private List<ISettingsTab> Tabs { get; }
|
||||
private int CurrentTab;
|
||||
|
||||
internal SettingsWindow(Plugin plugin) : base($"{Language.Settings_Title.Format(Plugin.PluginName)}###chat2-settings")
|
||||
{
|
||||
Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse;
|
||||
|
||||
SizeCondition = ImGuiCond.FirstUseEver;
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(475, 600),
|
||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
||||
};
|
||||
|
||||
Plugin = plugin;
|
||||
Mutable = new Configuration();
|
||||
|
||||
Tabs =
|
||||
[
|
||||
new Display(Mutable),
|
||||
new ChatLog(Plugin, Mutable),
|
||||
new Emote(Plugin, Mutable),
|
||||
new Preview(Mutable),
|
||||
new Fonts(Mutable),
|
||||
new ChatColours(Plugin, Mutable),
|
||||
new Tabs(Plugin, Mutable),
|
||||
new SettingsTabs.Privacy(Plugin, Mutable),
|
||||
new Database(Plugin, Mutable),
|
||||
new Miscellaneous(Mutable),
|
||||
new Changelog(Mutable),
|
||||
new About()
|
||||
];
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
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();
|
||||
}
|
||||
|
||||
private void Initialise()
|
||||
{
|
||||
Mutable.UpdateFrom(Plugin.Config, false);
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (ImGui.IsWindowAppearing())
|
||||
Initialise();
|
||||
|
||||
using (var table = ImRaii.Table("##chat2-settings-table", 2))
|
||||
{
|
||||
if (table.Success)
|
||||
{
|
||||
ImGui.TableSetupColumn("tab", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("settings", ImGuiTableColumnFlags.WidthStretch);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
var changed = false;
|
||||
for (var i = 0; i < Tabs.Count; i++)
|
||||
{
|
||||
if (!ImGui.Selectable($"{Tabs[i].Name}###tab-{i}", CurrentTab == i))
|
||||
continue;
|
||||
|
||||
CurrentTab = i;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
var style = ImGui.GetStyle();
|
||||
var height = ImGui.GetContentRegionAvail().Y - style.FramePadding.Y * 2 - style.ItemSpacing.Y - style.ItemInnerSpacing.Y * 2 - ImGui.CalcTextSize("A").Y;
|
||||
|
||||
using var child = ImRaii.Child("##chat2-settings", new Vector2(-1, height));
|
||||
if (child.Success)
|
||||
Tabs[CurrentTab].Draw(changed);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
var save = ImGui.Button(Language.Settings_Save);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button(Language.Settings_SaveAndClose)) {
|
||||
save = true;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button(Language.Settings_Discard)) {
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
const string buttonLabel = "Anna's Ko-fi";
|
||||
const string buttonLabel2 = "Infi's Ko-fi";
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, ColourUtil.RgbaToAbgr(0xFF5E5BFF)))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.RgbaToAbgr(0xFF7775FF)))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.RgbaToAbgr(0xFF4542FF)))
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFFFFFFFF))
|
||||
{
|
||||
var buttonWidth = ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2;
|
||||
var buttonWidth2 = ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2;
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X - buttonWidth - buttonWidth2);
|
||||
|
||||
if (ImGui.Button(buttonLabel2))
|
||||
Dalamud.Utility.Util.OpenLink("https://ko-fi.com/infiii");
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button(buttonLabel))
|
||||
Dalamud.Utility.Util.OpenLink("https://ko-fi.com/lojewalo");
|
||||
}
|
||||
|
||||
if (!save)
|
||||
return;
|
||||
|
||||
// calculate all conditions before updating config
|
||||
var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat;
|
||||
var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride;
|
||||
var fontChanged = Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
|
||||
|| Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
|
||||
|| Mutable.ItalicFontV2 != Plugin.Config.ItalicFontV2
|
||||
|| Mutable.ExtraGlyphRanges != Plugin.Config.ExtraGlyphRanges
|
||||
|| Mutable.UseHellionFont != Plugin.Config.UseHellionFont;
|
||||
var fontSizeChanged = Math.Abs(Mutable.SymbolsFontSizeV2 - Plugin.Config.SymbolsFontSizeV2) > 0.001
|
||||
|| Math.Abs(Mutable.FontSizeV2 - Plugin.Config.FontSizeV2) > 0.001;
|
||||
var italicStateChanged = Mutable.ItalicEnabled != Plugin.Config.ItalicEnabled;
|
||||
|
||||
Plugin.Config.UpdateFrom(Mutable, true);
|
||||
|
||||
// save after 60 frames have passed, which should hopefully not
|
||||
// commit any changes that cause a crash
|
||||
Plugin.DeferredSaveFrames = 60;
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
|
||||
if (fontChanged || fontSizeChanged || italicStateChanged)
|
||||
Plugin.FontManager.BuildFonts();
|
||||
|
||||
if (languageChanged)
|
||||
Plugin.LanguageChanged(Plugin.Interface.UiLanguage);
|
||||
|
||||
if (hideChanged)
|
||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
||||
|
||||
if (Plugin.Config.ShowEmotes)
|
||||
Task.Run(EmoteCache.LoadData);
|
||||
|
||||
Initialise();
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class About : ISettingsTab
|
||||
{
|
||||
public string Name => string.Format(Language.Options_About_Tab, Plugin.PluginName) + "###tabs-about";
|
||||
|
||||
private readonly List<string> Translators =
|
||||
[
|
||||
"q673135110", "Akizem", "d0tiKs",
|
||||
"Moonlight_Everlit", "Dark32", "andreycout",
|
||||
"Button_", "Cali666", "cassandra308",
|
||||
"lokinmodar", "jtabox", "AkiraYorumoto",
|
||||
"MKhayle", "elena.space", "imlisa",
|
||||
"andrei5125", "ShivaMaheshvara", "aislinn87",
|
||||
"nishinatsu051", "lichuyuan", "Risu64",
|
||||
"yummypillow", "witchymary", "Yuzumi",
|
||||
"zomsakura", "Sirayuki"
|
||||
];
|
||||
|
||||
internal About()
|
||||
{
|
||||
Translators.Sort((a, b) => string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Authors);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Discord);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama");
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Version);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedOrange, Plugin.Interface.Manifest.AssemblyVersion.ToString(3));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/JonKazama-Hellion/HellionChat/issues");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia"))
|
||||
Dalamud.Utility.Util.OpenLink("https://hellion-media.de");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/Infiziert90/ChatTwo");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P2);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// The translator list lives at the bottom of the About tab. Render
|
||||
// it directly inside the parent scroll container instead of a
|
||||
// fixed-height child — the previous "remaining space" calculation
|
||||
// shrank to zero (or below) once the About copy grew, which made
|
||||
// the section unreachable on smaller settings windows.
|
||||
using (var treeNode = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode))
|
||||
{
|
||||
if (treeNode)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false);
|
||||
foreach (var translator in Translators)
|
||||
ImGui.TextUnformatted(translator);
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Changelog : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Changelog_Tab + "###tabs-changelog";
|
||||
|
||||
internal Changelog(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Warning_NotImplemented);
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PrintChangelog, Language.Options_PrintChangelog_Name, Language.Options_PrintChangelog_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var changelog = Plugin.Interface.Manifest.Changelog;
|
||||
if (changelog != null)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Changelog_Header);
|
||||
ImGui.TextUnformatted($"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}");
|
||||
ImGui.Spacing();
|
||||
foreach (var sentence in changelog.Split("\n"))
|
||||
{
|
||||
if (sentence == string.Empty)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
var condition = sentence.StartsWith('-') || sentence.StartsWith(" -");
|
||||
using var indent = ImRaii.PushIndent(10.0f, true, condition);
|
||||
ImGui.TextUnformatted(sentence);
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class ChatColours : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_ChatColours_Tab + "###tabs-chat-colours";
|
||||
|
||||
internal ChatColours(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
|
||||
#if DEBUG
|
||||
// Users can set colours for ExtraChat linkshells in the ExtraChat plugin directly.
|
||||
var sortable = ChatTypeExt.SortOrder
|
||||
.SelectMany(entry => entry.Item2)
|
||||
.Where(type => !type.IsGm() && !type.IsExtraChatLinkshell())
|
||||
.ToHashSet();
|
||||
var total = Enum.GetValues<ChatType>()
|
||||
.Where(type => !type.IsGm() && !type.IsExtraChatLinkshell())
|
||||
.ToHashSet();
|
||||
if (sortable.Count != total.Count)
|
||||
{
|
||||
Plugin.Log.Warning($"There are {sortable.Count} sortable channels, but there are {total.Count} total channels.");
|
||||
total.ExceptWith(sortable);
|
||||
foreach (var missing in total)
|
||||
Plugin.Log.Information($"Missing {missing}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, $"{type}", Language.Options_ChatColours_Reset))
|
||||
Mutable.ChatColours.Remove(type);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.LongArrowAltDown, $"{type}", Language.Options_ChatColours_Import))
|
||||
{
|
||||
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
|
||||
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = Mutable.ChatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
|
||||
Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class ChatLog : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_ChatLog_Tab + "###tabs-chatlog";
|
||||
|
||||
internal ChatLog(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using (ImRaii.TextWrapPos(0.0f))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.KeepInputFocus, Language.Options_KeepInputFocus_Name, Language.Options_KeepInputFocus_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PlaySounds, Language.Options_PlaySounds_Name, Language.Options_PlaySounds_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.SidebarTabView, Language.Options_SidebarTabView_Name, string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowNoviceNetwork, Language.Options_ShowNoviceNetwork_Name, Language.Options_ShowNoviceNetwork_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowHideButton, Language.Options_ShowHideButton_Name, Language.Options_ShowHideButton_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.NativeItemTooltips, Language.Options_NativeItemTooltips_Name, string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.NativeItemTooltips)
|
||||
{
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_TooltipOffset_Name, Language.Options_TooltipOffset_Desc, ref Mutable.TooltipOffset, 1, 0f, 400f, $"{Mutable.TooltipOffset:N0}px", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_WindowOpacity_Name, ref Mutable.WindowAlpha, .25f, 0f, 100f, $"{Mutable.WindowAlpha:N2}%%", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.InputIntVertical(Language.Options_MaxLinesToShow_Name, Language.Options_MaxLinesToShow_Description, ref Mutable.MaxLinesToRender))
|
||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CanMove, Language.Options_CanMove_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CanResize, Language.Options_CanResize_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowTitleBar, Language.Options_ShowTitleBar_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowPopOutTitleBar, Language.Options_ShowPopOutTitleBar_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.OverrideStyle, Language.Options_OverrideStyle_Name, Language.Options_OverrideStyle_Name_Desc);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabForwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabForwardKeybind", ref Mutable.ChatTabForward);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabBackwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabBackwardKeybind", ref Mutable.ChatTabBackward);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_AdjustPosition_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
var pos = Plugin.ChatLogWindow.LastWindowPos;
|
||||
if (ImGui.DragFloat2($"##{Language.Options_AdjustPosition_Name}", ref pos, 1, 0, float.MaxValue, "%.0fpx"))
|
||||
Plugin.ChatLogWindow.Position = pos;
|
||||
ImGuiUtil.WarningText(Language.Options_AdjustPosition_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawAutoTellTabsSection();
|
||||
}
|
||||
|
||||
if (!Mutable.OverrideStyle)
|
||||
return;
|
||||
|
||||
var styles = StyleModel.GetConfiguredStyles();
|
||||
if (styles == null)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_OverrideStyle_NotAvailable);
|
||||
ImGui.Spacing();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStyle = Mutable.ChosenStyle ?? Language.Options_OverrideStyle_NotSelected;
|
||||
using var combo = ImRaii.Combo(Language.Options_OverrideStyleDropdown_Name, currentStyle);
|
||||
if (combo)
|
||||
{
|
||||
foreach (var style in styles)
|
||||
if (ImGui.Selectable(style.Name, Mutable.ChosenStyle == style.Name))
|
||||
Mutable.ChosenStyle = style.Name;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
private void DrawAutoTellTabsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.ChatLog_AutoTellTabs_Section_Title);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Enable_Name, ref Mutable.EnableAutoTellTabs);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description);
|
||||
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
var limit = Mutable.AutoTellTabsLimit;
|
||||
if (ImGui.SliderInt(HellionStrings.ChatLog_AutoTellTabs_Limit_Name, ref limit, 1, 50))
|
||||
{
|
||||
Mutable.AutoTellTabsLimit = limit;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Compact_Name, ref Mutable.AutoTellTabsCompactDisplay);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, ref Mutable.AutoTellTabsShowGreetedToggle);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpText(HellionStrings.ChatLog_AutoTellTabs_PreloadHint);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.WarningText(HellionStrings.ChatLog_AutoTellTabs_ConflictHint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Database : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Database_Tab + "###tabs-database";
|
||||
|
||||
internal Database(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
private bool ShowAdvanced;
|
||||
|
||||
private long DatabaseLastRefreshTicks;
|
||||
private long DatabaseSize;
|
||||
private long DatabaseLogSize;
|
||||
private int DatabaseMessageCount;
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
if (changed)
|
||||
ShowAdvanced = ImGui.GetIO().KeyShift;
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.DatabaseBattleMessages, Language.Options_DatabaseBattleMessages_Name, Language.Options_DatabaseBattleMessages_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.OptionCheckbox(ref Mutable.LoadPreviousSession, Language.Options_LoadPreviousSession_Name, Language.Options_LoadPreviousSession_Description))
|
||||
if (Mutable.LoadPreviousSession)
|
||||
Mutable.FilterIncludePreviousSessions = true;
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.OptionCheckbox(ref Mutable.FilterIncludePreviousSessions, Language.Options_FilterIncludePreviousSessions_Name, Language.Options_FilterIncludePreviousSessions_Description))
|
||||
if (!Mutable.FilterIncludePreviousSessions)
|
||||
Mutable.LoadPreviousSession = false;
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var old = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat.db"));
|
||||
var migratedOld = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db"));
|
||||
if (old.Exists || migratedOld.Exists)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Database_Old_Heading);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_Database_Old_Delete, Language.Options_Database_Old_Delete_Tooltip))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (old.Exists)
|
||||
old.Delete();
|
||||
else
|
||||
migratedOld.Delete();
|
||||
WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Success, NotificationType.Success);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Plugin.Log.Error(e, "Unable to delete old database");
|
||||
WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Error, NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Database_Metadata_Heading);
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Refresh the database size and message count every 5 seconds to avoid
|
||||
// constant stat calls and spamming the database.
|
||||
if (DatabaseLastRefreshTicks + 5 * 1000 < Environment.TickCount64)
|
||||
{
|
||||
DatabaseSize = Plugin.MessageManager.Store.DatabaseSize();
|
||||
DatabaseLogSize = Plugin.MessageManager.Store.DatabaseLogSize();
|
||||
DatabaseMessageCount = Plugin.MessageManager.Store.MessageCount();
|
||||
DatabaseLastRefreshTicks = Environment.TickCount64;
|
||||
}
|
||||
|
||||
// Copy the directory path instead of the file path so people can
|
||||
// paste it into their file explorer.
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Path, MessageManager.DatabasePath()));
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
var path = Path.GetDirectoryName(MessageManager.DatabasePath());
|
||||
ImGui.SetClipboardText(path);
|
||||
WrapperUtil.AddNotification(Language.Options_Database_Metadata_CopyConfigPathNotification, NotificationType.Info);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
ImGuiUtil.Tooltip(Language.Options_Database_Metadata_CopyConfigPath);
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Size, StringUtil.BytesToString(DatabaseSize)));
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseSize));
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_LogSize, StringUtil.BytesToString(DatabaseLogSize)));
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(StringUtil.BytesToString(DatabaseLogSize));
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_MessageCount, DatabaseMessageCount));
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_ClearDatabase_Button, Language.Options_ClearDatabase_Tooltip))
|
||||
{
|
||||
Plugin.Log.Warning("Clearing messages from database");
|
||||
Plugin.MessageManager.Store.ClearMessages();
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
|
||||
// Refresh on next draw
|
||||
DatabaseLastRefreshTicks = 0;
|
||||
WrapperUtil.AddNotification(Language.Options_ClearDatabase_Success, NotificationType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
if (!ShowAdvanced)
|
||||
return;
|
||||
|
||||
using var treeNode = ImRaii.TreeNode(Language.Options_Database_Advanced);
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning);
|
||||
if (ImGuiUtil.CtrlShiftButton("Perform maintenance", "Ctrl+Shift: MessageManager.Store.PerformMaintenance()"))
|
||||
Plugin.MessageManager.Store.PerformMaintenance();
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton("Reload messages from database", "Ctrl+Shift: MessageManager.FilterAllTabs()"))
|
||||
{
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton("Inject 10,000 messages", "Ctrl+Shift: creates 10,000 unique messages (async)"))
|
||||
new Thread(() => InsertMessages(10_000)).Start();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
private void InsertMessages(int count)
|
||||
{
|
||||
Plugin.Log.Info($"Inserting {count} messages due to user request");
|
||||
|
||||
// Generate
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var playerName = Plugin.PlayerState.CharacterName;
|
||||
var worldId = Plugin.PlayerState.HomeWorld.ValueNullable?.RowId ?? 0;
|
||||
var senderSource = new SeStringBuilder()
|
||||
.AddText("<")
|
||||
.Add(new PlayerPayload(playerName, worldId))
|
||||
.AddText("Random Message")
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.AddText(">: ")
|
||||
.Build();
|
||||
var senderChunks = ChunkUtil.ToChunks(senderSource, ChunkSource.Sender, ChatType.Debug).ToList();
|
||||
var messages = new List<Message>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var contentSource = new SeStringBuilder()
|
||||
.AddText("Random message payload - ")
|
||||
.AddItalics(Guid.NewGuid().ToString())
|
||||
.Build();
|
||||
var contentChunks = ChunkUtil.ToChunks(contentSource, ChunkSource.Content, ChatType.Debug).ToList();
|
||||
|
||||
var chatCode = new ChatCode(XivChatType.Say, 0, 0);
|
||||
messages.Add(new Message(
|
||||
Guid.NewGuid(),
|
||||
Plugin.MessageManager.CurrentContentId,
|
||||
Plugin.MessageManager.CurrentContentId,
|
||||
DateTimeOffset.UtcNow,
|
||||
chatCode,
|
||||
senderChunks,
|
||||
contentChunks,
|
||||
senderSource,
|
||||
contentSource,
|
||||
Guid.Empty
|
||||
));
|
||||
}
|
||||
|
||||
var elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.Log.Info($"Crafted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
|
||||
|
||||
// Insert
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
foreach (var message in messages)
|
||||
Plugin.MessageManager.Store.UpsertMessage(message);
|
||||
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.Log.Info($"Upserted {count} messages in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
|
||||
|
||||
// Clear tabs during framework frame
|
||||
Plugin.Framework.Run(() =>
|
||||
{
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.Log.Info($"Cleared {Plugin.Config.Tabs.Count} tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
|
||||
}).Wait();
|
||||
|
||||
// Fetch and filter during framework frame
|
||||
Plugin.Framework.Run(() =>
|
||||
{
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
// Intentionally synchronous
|
||||
Plugin.MessageManager.FilterAllTabs();
|
||||
elapsedTicks = stopwatch.ElapsedTicks;
|
||||
stopwatch.Stop();
|
||||
Plugin.Log.Info($"Fetched and filtered all tabs in {elapsedTicks} ticks ({elapsedTicks / TimeSpan.TicksPerMillisecond}ms)");
|
||||
}).Wait();
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Display : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Display_Tab + "###tabs-display";
|
||||
|
||||
internal Display(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideChat, Language.Options_HideChat_Name, Language.Options_HideChat_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideDuringCutscenes, Language.Options_HideDuringCutscenes_Name, string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenNotLoggedIn, Language.Options_HideWhenNotLoggedIn_Name, string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenUiHidden, Language.Options_HideWhenUiHidden_Name, string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideInLoadingScreens, Language.Options_HideInLoadingScreens_Name, string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideInBattle, Language.Options_HideInBattle_Name, Language.Options_HideInBattle_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenInactive, Language.Options_HideWhenInactive_Name, Language.Options_HideWhenInactive_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.HideWhenInactive)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.InputIntVertical(Language.Options_InactivityHideTimeout_Name,
|
||||
Language.Options_InactivityHideTimeout_Description, ref Mutable.InactivityHideTimeout, 1, 10);
|
||||
// Enforce a minimum of 2 seconds to avoid people soft locking
|
||||
// themselves.
|
||||
Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout);
|
||||
ImGui.Spacing();
|
||||
|
||||
// This setting conflicts with HideInBattle, so it's disabled.
|
||||
using (ImRaii.Disabled(Mutable.HideInBattle))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.InactivityHideActiveDuringBattle,
|
||||
Language.Options_InactivityHideActiveDuringBattle_Name,
|
||||
Language.Options_InactivityHideActiveDuringBattle_Description);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
using var channelTree = ImRaii.TreeNode(Language.Options_InactivityHideChannels_Name);
|
||||
if (channelTree.Success)
|
||||
{
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_All_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = TabsUtil.AllChannels();
|
||||
Mutable.InactivityHideExtraChatAll = true;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_None_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = [];
|
||||
Mutable.InactivityHideExtraChatAll = false;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, Mutable.InactivityHideChannelsV2);
|
||||
ImGuiUtil.ExtraChatSelector(Language.Options_Tabs_ExtraChatChannels,
|
||||
ref Mutable.InactivityHideExtraChatAll, Mutable.InactivityHideExtraChatChannels);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.Use24HourClock, Language.Options_Use24HourClock_Name, Language.Options_Use24HourClock_Description);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PrettierTimestamps, Language.Options_PrettierTimestamps_Name, Language.Options_PrettierTimestamps_Description);
|
||||
|
||||
if (Mutable.PrettierTimestamps)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.MoreCompactPretty, Language.Options_MoreCompactPretty_Name, Language.Options_MoreCompactPretty_Description);
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideSameTimestamps, Language.Options_HideSameTimestamps_Name, Language.Options_HideSameTimestamps_Description);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CollapseDuplicateMessages, Language.Options_CollapseDuplicateMessages_Name, Language.Options_CollapseDuplicateMessages_Description);
|
||||
if (Mutable.CollapseDuplicateMessages)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CollapseKeepUniqueLinks, Language.Options_CollapseDuplicateMsgUniqueLink_Name, Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Emote : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Emote_Tab + "###tabs-emote";
|
||||
|
||||
private static SearchSelector.SelectorPopupOptions? WordPopupOptions;
|
||||
|
||||
internal Emote(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
|
||||
WordPopupOptions = new SearchSelector.SelectorPopupOptions
|
||||
{
|
||||
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private SearchSelector.SelectorPopupOptions RefillSheet()
|
||||
{
|
||||
return new SearchSelector.SelectorPopupOptions
|
||||
{
|
||||
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowEmotes, Language.Options_ShowEmotes_Name, Language.Options_ShowEmotes_Desc);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes);
|
||||
ImGui.Spacing();
|
||||
|
||||
WordPopupOptions ??= RefillSheet();
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done && WordPopupOptions.FilteredSheet.Length == 0)
|
||||
WordPopupOptions = RefillSheet();
|
||||
|
||||
var buttonWidth = ImGui.GetContentRegionAvail().X / 3;
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
|
||||
|
||||
if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions))
|
||||
Mutable.BlockedEmotes.Add(newWord);
|
||||
|
||||
using(var table = ImRaii.Table("##BlockedWords", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (table)
|
||||
{
|
||||
ImGui.TableSetupColumn(Language.Options_Emote_EmoteTable);
|
||||
ImGui.TableSetupColumn("##Del", ImGuiTableColumnFlags.WidthStretch, 0.07f);
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var copiedList = Mutable.BlockedEmotes.ToArray();
|
||||
foreach (var word in copiedList)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.Button($"##{word}Del", FontAwesomeIcon.Trash, !ImGui.GetIO().KeyCtrl))
|
||||
Mutable.BlockedEmotes.Remove(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Emote_EmoteStats);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done)
|
||||
ImGui.TextColored(ImGuiColors.HealerGreen, Language.Options_Emote_Ready);
|
||||
else
|
||||
ImGui.TextColored(ImGuiColors.DPSRed, Language.Options_Emote_NotReady);
|
||||
|
||||
ImGui.TextUnformatted($"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}");
|
||||
using (var emoteTable = ImRaii.Table("##LoadedEmotes", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (emoteTable)
|
||||
{
|
||||
ImGui.TableSetupColumn("##word1");
|
||||
ImGui.TableSetupColumn("##word2");
|
||||
ImGui.TableSetupColumn("##word3");
|
||||
ImGui.TableSetupColumn("##word4");
|
||||
ImGui.TableSetupColumn("##word5");
|
||||
|
||||
foreach (var word in EmoteCache.SortedCodeArray)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
public class Fonts : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Fonts_Tab + "###tabs-fonts";
|
||||
|
||||
internal Fonts(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool _)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (!Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(Language.Options_Font_Name, Mutable.GlobalFontV2, false, ref _);
|
||||
globalChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.GlobalFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##global"))
|
||||
Mutable.GlobalFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Font_Description, Plugin.PluginName));
|
||||
ImGuiUtil.WarningText(Language.Options_Font_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
// LocaleNames being null means it is likely a game font which all support JP symbols
|
||||
var japaneseChooser = ImGuiUtil.FontChooser(Language.Options_JapaneseFont_Name, Mutable.JapaneseFontV2, false, ref _, id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false, "いろはにほへと ちりぬるを");
|
||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.JapaneseFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##japanese"))
|
||||
Mutable.JapaneseFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium), SizePt = 12.75f };
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
var italicChooser = ImGuiUtil.FontChooser(Language.Options_ItalicFont_Name, Mutable.ItalicFontV2, true, ref Mutable.ItalicEnabled);
|
||||
italicChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.ItalicFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##italic"))
|
||||
{
|
||||
Mutable.ItalicEnabled = false;
|
||||
Mutable.ItalicFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Italic_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName));
|
||||
|
||||
var range = (int) Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int) extra);
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges) range;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_SymbolsFontSize_Name, ref Mutable.SymbolsFontSizeV2);
|
||||
ImGuiUtil.HelpText(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Miscellaneous(Configuration mutable) : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; } = mutable;
|
||||
public string Name => Language.Options_Miscellaneous_Tab + "###tabs-miscellaneous";
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Language_Name, Mutable.LanguageOverride.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LanguageOverride>())
|
||||
if (ImGui.Selectable(language.Name()))
|
||||
Mutable.LanguageOverride = language;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Language_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_CommandHelpSide_Name, Mutable.CommandHelpSide.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var side in Enum.GetValues<CommandHelpSide>())
|
||||
if (ImGui.Selectable(side.Name(), Mutable.CommandHelpSide == side))
|
||||
Mutable.CommandHelpSide = side;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_KeybindMode_Name, Mutable.KeybindMode.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<KeybindMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.Name(), Mutable.KeybindMode == mode))
|
||||
Mutable.KeybindMode = mode;
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(mode.Tooltip() ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
|
||||
ImGuiUtil.HelpText(Language.Options_SortAutoTranslate_Description);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Preview : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => $"{Language.Options_Preview_Tab}###tabs-preview";
|
||||
|
||||
internal Preview(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Preview_Name, Mutable.PreviewPosition.Name()))
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var position in Enum.GetValues<PreviewPosition>())
|
||||
if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position))
|
||||
Mutable.PreviewPosition = position;
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpText(Language.Options_Preview_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.InputIntVertical(Language.Options_PreviewMinimum_Name, Language.Options_PreviewMinimum_Description, ref Mutable.PreviewMinimum))
|
||||
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.OnlyPreviewIf, Language.Options_PreviewOnlyIf_Name, Language.Options_PreviewOnlyIf_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using System.Text;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
|
||||
public static class MemoryUtil
|
||||
{
|
||||
public static unsafe void PrintMemoryArea(nint address, int length)
|
||||
{
|
||||
var ptr = (byte*)address;
|
||||
var str = new StringBuilder("\n");
|
||||
for(var i = 0; i < length; i++)
|
||||
{
|
||||
str.Append($"{ptr![i]:X02}");
|
||||
|
||||
if (i == 0)
|
||||
continue;
|
||||
|
||||
if ((i+1) % 16 == 0)
|
||||
str.Append('\n');
|
||||
else if ((i+1) % 4 == 0)
|
||||
str.Append(' ');
|
||||
}
|
||||
|
||||
Plugin.Log.Information(str.ToString());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
Executable
+31
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HellionChat", "HellionChat\HellionChat.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|x64.Build.0 = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{739F75E6-B65F-41EF-9D90-F7BC519E4875}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs.
|
||||
//
|
||||
@@ -183,6 +183,22 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
// v0.6.1 — if the victim is currently popped out, tear down the
|
||||
// matching Popout window first. Otherwise the window stays in
|
||||
// PopOutWindows + WindowSystem and renders empty / re-spawns on the
|
||||
// next AddPopOutsToDraw tick. Latent since pop-outs were introduced;
|
||||
// becomes visible with AutoTellTabsOpenAsPopout where dropping a
|
||||
// popped tab is now a routine code path.
|
||||
if (victim.Tab.PopOut)
|
||||
{
|
||||
var popout = _plugin.ChatLogWindow.ActivePopouts
|
||||
.FirstOrDefault(p => p.TabIdentifier == victim.Tab.Identifier);
|
||||
if (popout != null)
|
||||
{
|
||||
popout.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.RemoveAt(victim.Index);
|
||||
|
||||
// Re-anchor the active tab so the user does not silently end up on
|
||||
@@ -200,9 +216,24 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
// Preload first so the tab opens with chronological history above
|
||||
// the current message — and so a slow DB query never causes a
|
||||
// visible "empty tab, then history pops in" effect on screen.
|
||||
PreloadHistory(tab, partner.Name, partner.World);
|
||||
// The current message is already persisted in the store by the
|
||||
// time MessageProcessed fires (see MessageManager.cs: UpsertMessage
|
||||
// runs before the event), so we have to exclude it explicitly to
|
||||
// avoid the separator landing below the live tell.
|
||||
PreloadHistory(tab, partner.Name, partner.World, currentMessage.Id);
|
||||
|
||||
tab.AddMessage(currentMessage, unread: true);
|
||||
|
||||
// Hellion Chat v0.6.1 — opt-in: open new /tell tabs directly as a
|
||||
// pop-out window. Set BEFORE Tabs.Add so the next render-tick's
|
||||
// AddPopOutsToDraw() sees PopOut=true and spawns the Popout window
|
||||
// alongside the tab going into the list. No SaveConfig() because
|
||||
// auto-tell tabs are IsTempTab (session-only, never persisted).
|
||||
if (Plugin.Config.AutoTellTabsOpenAsPopout)
|
||||
{
|
||||
tab.PopOut = true;
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.Add(tab);
|
||||
}
|
||||
|
||||
@@ -238,7 +269,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
return $"{playerName}@World{worldRowId}";
|
||||
}
|
||||
|
||||
private void PreloadHistory(Tab tab, string senderName, uint senderWorld)
|
||||
private void PreloadHistory(Tab tab, string senderName, uint senderWorld, Guid currentMessageId)
|
||||
{
|
||||
var preloadCount = Plugin.Config.AutoTellTabsHistoryPreload;
|
||||
if (preloadCount <= 0)
|
||||
@@ -248,13 +279,21 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
// Pull one extra row because the live tell that triggered this
|
||||
// spawn is already in the store and would otherwise eat one of
|
||||
// the user's preload-budget slots.
|
||||
var history = _store.GetTellHistoryWithSender(
|
||||
_messageManager.CurrentContentId,
|
||||
senderName,
|
||||
senderWorld,
|
||||
preloadCount);
|
||||
preloadCount + 1);
|
||||
|
||||
if (history.Count == 0)
|
||||
var historicMessages = history
|
||||
.Where(m => m.Id != currentMessageId)
|
||||
.Take(preloadCount)
|
||||
.ToList();
|
||||
|
||||
if (historicMessages.Count == 0)
|
||||
{
|
||||
// No prior tells with this player — leave the tab to start
|
||||
// empty so the user does not see a "history loaded" marker
|
||||
@@ -265,7 +304,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
// The history list is already oldest-first, so a plain AddPrune
|
||||
// loop produces the chronological order the user expects to see
|
||||
// when the tab opens.
|
||||
foreach (var message in history)
|
||||
foreach (var message in historicMessages)
|
||||
{
|
||||
tab.Messages.AddPrune(message, MessageManager.MessageDisplayLimit);
|
||||
}
|
||||
@@ -343,6 +382,27 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
var lastIndexValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
var currentWasTempTab = lastIndexValid && Plugin.Config.Tabs[lastIndex].IsTempTab;
|
||||
|
||||
// v0.6.1 — symmetric to DropOldestTempTab cleanup: tear down any
|
||||
// popped-out temp tab windows before removing the tabs themselves,
|
||||
// otherwise PopOutWindows + WindowSystem keep ghost entries until
|
||||
// the next plugin reload. Especially relevant once Auto-Pop-Out is
|
||||
// enabled — every logout would otherwise leak as many ghosts as
|
||||
// there were active /tell pop-outs.
|
||||
var poppedTempTabIds = Plugin.Config.Tabs
|
||||
.Where(t => t.IsTempTab && t.PopOut)
|
||||
.Select(t => t.Identifier)
|
||||
.ToList();
|
||||
if (poppedTempTabIds.Count > 0)
|
||||
{
|
||||
var poppedSet = poppedTempTabIds.ToHashSet();
|
||||
foreach (var popout in _plugin.ChatLogWindow.ActivePopouts
|
||||
.Where(p => poppedSet.Contains(p.TabIdentifier))
|
||||
.ToList())
|
||||
{
|
||||
popout.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
// Force a switch to tab 0 if the active tab was a temp tab OR
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Linq;
|
||||
using HellionChat.Resources;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
internal static class ChatTwoConflictDetector
|
||||
{
|
||||
private const string UpstreamInternalName = "ChatTwo";
|
||||
|
||||
public static void ThrowIfChatTwoIsLoaded(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
var conflict = pluginInterface.InstalledPlugins
|
||||
.FirstOrDefault(p =>
|
||||
p.InternalName == UpstreamInternalName &&
|
||||
p.IsLoaded);
|
||||
|
||||
if (conflict is null)
|
||||
return;
|
||||
|
||||
var message = HellionStrings.ChatTwoConflictTitle + "\n\n" +
|
||||
HellionStrings.ChatTwoConflictBody + "\n\n" +
|
||||
HellionStrings.ChatTwoConflictAction;
|
||||
|
||||
throw new System.InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using MessagePack;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
[Union(0, typeof(TextChunk))]
|
||||
[Union(1, typeof(IconChunk))]
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
public class ChatCode
|
||||
{
|
||||
@@ -91,13 +91,10 @@ public class ChatCode
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
if (obj is not ChatCode code)
|
||||
return false;
|
||||
|
||||
return GetHashCode() == code.GetHashCode();
|
||||
return Type == code.Type && Source == code.Source && Target == code.Target;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
[Flags]
|
||||
public enum ChatSource : ushort
|
||||
@@ -1,6 +1,6 @@
|
||||
using ChatTwo.Resources;
|
||||
using HellionChat.Resources;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
internal static class ChatSourceExt
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
public enum ChatType : ushort
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Config;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
internal static class ChatTypeExt
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
public enum InputChannel : uint
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace ChatTwo.Code;
|
||||
namespace HellionChat.Code;
|
||||
|
||||
internal static class InputChannelExt
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Game.Command;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
internal sealed class Commands : IDisposable
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
@@ -10,7 +10,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
[Serializable]
|
||||
public class ConfigKeyBind
|
||||
@@ -34,10 +34,29 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int LatestVersion = 9;
|
||||
private const int LatestVersion = 16;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
// v1.1.0 — Theme-Engine. Slug-basiert; ThemeRegistry liefert das Objekt.
|
||||
public string Theme = "hellion-arctic";
|
||||
|
||||
// v1.1.0 — Globale Window-Opacity, theme-übergreifend. Migration aus
|
||||
// HellionThemeWindowOpacity beim Bump v13 → v14.
|
||||
public float WindowOpacity = 0.85f;
|
||||
|
||||
|
||||
// v1.1.0 — Felder für künftige UI-Toggles (v1.2.0 / v1.3.0). Werden
|
||||
// vorab angelegt, damit später keine Migration nötig ist.
|
||||
public bool ReduceMotion;
|
||||
// v1.2.1 — Default geflippt von false → true. Card-Rows-Layout aus
|
||||
// v1.2.0 wurde als zu dicht empfunden; Single-Line `[HH:mm] Sender:
|
||||
// Text` ist besser lesbar und platzsparender. Bestand-User mit aktiv
|
||||
// false werden durch die v15→v16-Migration auf den neuen Default
|
||||
// gehoben (Heuristik: wer in v1.2.0 false hatte, hatte den damals
|
||||
// neu eingeführten Default — kaum jemand hat aktiv abgeschaltet).
|
||||
public bool UseCompactDensity = true;
|
||||
|
||||
// Hellion Chat — Privacy filter (DSGVO Art. 25 Privacy by Default).
|
||||
// Master-switch defaults to true; set false to restore upstream behavior.
|
||||
public bool PrivacyFilterEnabled = true;
|
||||
@@ -67,15 +86,6 @@ public class Configuration : IPluginConfiguration
|
||||
// ChatTwo users skip it because the v6→v7 migration sets the flag.
|
||||
public bool FirstRunCompleted;
|
||||
|
||||
// Hellion Chat global ImGui theme — applied to every plugin window in
|
||||
// Plugin.Draw. Default ON; users who prefer the upstream Dalamud look
|
||||
// can flip this off in the Privacy tab.
|
||||
public bool HellionThemeEnabled = true;
|
||||
|
||||
// Window background opacity, 0.5–1.0. Lower values make the plugin
|
||||
// panes more glass-like so the game shines through. Default ~92%.
|
||||
public float HellionThemeWindowOpacity = 0.92f;
|
||||
|
||||
// Use the bundled Exo 2 font (OFL-1.1) for the regular plugin font
|
||||
// instead of whatever GlobalFontV2.FontId points at. Default ON so a
|
||||
// fresh install gets the Hellion typography out-of-the-box; flip OFF
|
||||
@@ -101,6 +111,33 @@ public class Configuration : IPluginConfiguration
|
||||
// want the auto tabs themselves without the extra UI affordance.
|
||||
public bool AutoTellTabsShowGreetedToggle;
|
||||
|
||||
// Hellion Chat — One-Time-Hint-Banner that introduces the v0.6.0 pop-out
|
||||
// input feature. Set to true once the user dismisses the banner from a
|
||||
// pop-out window; never reset after that.
|
||||
public bool SeenPopOutInputHint;
|
||||
|
||||
// Hellion Chat — v0.6.0 master switch for the pop-out input bar.
|
||||
// Global on purpose: per-tab makes no sense for Auto-Tell-Tabs which
|
||||
// are session-only and would force the user to re-enable it for every
|
||||
// new conversation. Default flipped to ON in v0.6.1 (was OFF in v0.6.0)
|
||||
// because tester feedback called the manual toggle "umständlich, wirkt
|
||||
// unfertig". v11 → v12 migration applies the same flip to existing users.
|
||||
public bool PopOutInputEnabled = true;
|
||||
|
||||
// Hellion Chat — v0.6.1 One-Time-Hint-Banner that introduces the
|
||||
// chat-header pop-out toolbar button and reminds about the pop-out
|
||||
// input default flip. Set to true once the user dismisses the banner
|
||||
// from the main chat window; never reset after that.
|
||||
public bool SeenPopOutHeaderHint;
|
||||
|
||||
// Hellion Chat — v0.6.1 opt-in: when true, AutoTellTabsService.SpawnTempTab
|
||||
// sets tab.PopOut = true on every new auto-tell tab so the conversation
|
||||
// pops out as its own window directly. Closing the pop-out returns the
|
||||
// tab to the sidebar via the standard Popout.OnClose() flow. Default OFF
|
||||
// because the existing sidebar workflow is what most users (especially
|
||||
// club greeters tracking many parallel tells) expect by default.
|
||||
public bool AutoTellTabsOpenAsPopout;
|
||||
|
||||
public int GetRetentionDays(ChatType type)
|
||||
{
|
||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||
@@ -116,6 +153,11 @@ public class Configuration : IPluginConfiguration
|
||||
public bool HideWhenUiHidden = true;
|
||||
public bool HideInLoadingScreens;
|
||||
public bool HideInBattle;
|
||||
// v1.2.1 — Default geflippt false → true. Hellion-UI im NG+-Menü
|
||||
// versteckt zu halten ist konsistent mit den anderen Hide-Defaults
|
||||
// (Cutscenes, Logged-out, UI-Hidden) — UI-out-of-the-way bei Story-
|
||||
// Sequenzen.
|
||||
public bool HideInNewGamePlusMenu = true;
|
||||
public bool HideWhenInactive;
|
||||
public int InactivityHideTimeout = 10;
|
||||
public bool InactivityHideActiveDuringBattle = true;
|
||||
@@ -130,7 +172,10 @@ public class Configuration : IPluginConfiguration
|
||||
public bool NativeItemTooltips = true;
|
||||
public bool PrettierTimestamps = true;
|
||||
public bool MoreCompactPretty;
|
||||
public bool HideSameTimestamps;
|
||||
// v1.2.1 — Default geflippt false → true. Wiederholte Zeitstempel
|
||||
// innerhalb derselben Minute lesen sich als Rauschen; ein einziger
|
||||
// Timestamp pro Minute reicht aus um die Konversation zu verorten.
|
||||
public bool HideSameTimestamps = true;
|
||||
public bool ShowNoviceNetwork;
|
||||
// Hellion Chat — vertical sidebar tab layout reads better than the
|
||||
// horizontal tab strip in the company of Auto-Tell-Tabs (a club
|
||||
@@ -147,7 +192,7 @@ public class Configuration : IPluginConfiguration
|
||||
public LanguageOverride LanguageOverride = LanguageOverride.None;
|
||||
public bool CanMove = true;
|
||||
public bool CanResize = true;
|
||||
public bool ShowTitleBar;
|
||||
public bool ShowTitleBar = true;
|
||||
public bool ShowPopOutTitleBar = true;
|
||||
public bool DatabaseBattleMessages;
|
||||
public bool LoadPreviousSession;
|
||||
@@ -157,8 +202,16 @@ public class Configuration : IPluginConfiguration
|
||||
public bool CollapseKeepUniqueLinks;
|
||||
public bool PlaySounds = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public int MaxLinesToRender = 10_000; // 1-10000
|
||||
public bool Use24HourClock;
|
||||
// v1.2.1 — Default gesenkt 5000 → 2500. 5000 ist auf Mid-Range-
|
||||
// Hardware bei langen Sessions spürbar langsamer (Card-Layout
|
||||
// re-Layout pro Frame), 2500 deckt eine typische Stunde Chat ab
|
||||
// und bleibt smooth. User die mehr brauchen können bis 10000 hoch.
|
||||
public int MaxLinesToRender = 2_500; // 1-10000
|
||||
// Default ON to match a German / European 24h locale. The
|
||||
// ChatLogWindow.cs format-flip in v0.5.1 honours this strictly via
|
||||
// CultureInfo.InvariantCulture so the result is consistent across
|
||||
// host locales.
|
||||
public bool Use24HourClock = true;
|
||||
|
||||
public bool ShowEmotes = true;
|
||||
public HashSet<string> BlockedEmotes = [];
|
||||
@@ -186,12 +239,24 @@ public class Configuration : IPluginConfiguration
|
||||
};
|
||||
|
||||
public float TooltipOffset;
|
||||
public float WindowAlpha = 100f;
|
||||
public Dictionary<ChatType, uint> ChatColours = new();
|
||||
public List<Tab> Tabs = [];
|
||||
// v1.2.1 — Default-Chat-Farben sind das Hellion-Brand-Preset. Der
|
||||
// First-Run-Wizard bietet keine Theme-/Preset-Wahl an, daher kriegen
|
||||
// neue User die Hellion-Brand-Farben out-of-the-box (Cyan-Familie für
|
||||
// Standard/Tell, Ember/Warning für laute Channels). Bestand-User mit
|
||||
// leerem ChatColours-Dict werden durch die v15→v16-Migration auf das
|
||||
// Preset gehoben; User die bereits Custom-Farben haben, bleiben.
|
||||
public Dictionary<ChatType, uint> ChatColours = BuildDefaultChatColours();
|
||||
|
||||
public bool OverrideStyle;
|
||||
public string? ChosenStyle;
|
||||
private static Dictionary<ChatType, uint> BuildDefaultChatColours()
|
||||
{
|
||||
var defaults = new Dictionary<ChatType, uint>();
|
||||
foreach (var (channel, colour) in HellionChat.Resources.ChatColourPresets.All["Hellion"].Colours)
|
||||
defaults[channel] = colour;
|
||||
return defaults;
|
||||
}
|
||||
|
||||
public bool ColorSelectedInputChannelButton = true;
|
||||
public List<Tab> Tabs = [];
|
||||
|
||||
public ConfigKeyBind? ChatTabForward;
|
||||
public ConfigKeyBind? ChatTabBackward;
|
||||
@@ -208,6 +273,7 @@ public class Configuration : IPluginConfiguration
|
||||
HideWhenUiHidden = other.HideWhenUiHidden;
|
||||
HideInLoadingScreens = other.HideInLoadingScreens;
|
||||
HideInBattle = other.HideInBattle;
|
||||
HideInNewGamePlusMenu = other.HideInNewGamePlusMenu;
|
||||
HideWhenInactive = other.HideWhenInactive;
|
||||
InactivityHideTimeout = other.InactivityHideTimeout;
|
||||
InactivityHideActiveDuringBattle = other.InactivityHideActiveDuringBattle;
|
||||
@@ -243,7 +309,10 @@ public class Configuration : IPluginConfiguration
|
||||
MaxLinesToRender = other.MaxLinesToRender;
|
||||
Use24HourClock = other.Use24HourClock;
|
||||
ShowEmotes = other.ShowEmotes;
|
||||
BlockedEmotes = other.BlockedEmotes;
|
||||
// Deep-copy the set so the live and mutable Configuration instances don't share state
|
||||
// — a HashSet reference assignment would cause edits in the settings window to leak
|
||||
// into the live config before the user clicks Save.
|
||||
BlockedEmotes = new HashSet<string>(other.BlockedEmotes);
|
||||
FontsEnabled = other.FontsEnabled;
|
||||
ItalicEnabled = other.ItalicEnabled;
|
||||
ExtraGlyphRanges = other.ExtraGlyphRanges;
|
||||
@@ -253,21 +322,42 @@ public class Configuration : IPluginConfiguration
|
||||
ItalicFontV2 = other.ItalicFontV2;
|
||||
SymbolsFontSizeV2 = other.SymbolsFontSizeV2;
|
||||
TooltipOffset = other.TooltipOffset;
|
||||
WindowAlpha = other.WindowAlpha;
|
||||
ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||
ColorSelectedInputChannelButton = other.ColorSelectedInputChannelButton;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs are session-only and therefore
|
||||
// never present in a disk-loaded copy. Keep the live temp tabs of
|
||||
// *this* configuration alive across an UpdateFrom so a settings
|
||||
// save (or sidebar-mode toggle) does not silently destroy the
|
||||
// user's open tell conversations. Persistent tabs from `other`
|
||||
// still get the regular clone-replace treatment.
|
||||
// user's open tell conversations.
|
||||
//
|
||||
// For persistent tabs we go through Tab.Clone() which intentionally
|
||||
// does NOT copy the NonSerialized Messages list (avoids shared
|
||||
// mutable state on disk-load). On a settings save that means the
|
||||
// chat history for every persistent tab would be wiped — bug
|
||||
// reported by Flo 2026-05-05. We work around it by capturing the
|
||||
// live MessageList (and LastSendUnread counter) by Identifier
|
||||
// before the replace, then restoring it onto the freshly cloned
|
||||
// tabs whose Identifier survives Tab.Clone(). New tabs added in
|
||||
// settings get a fresh empty MessageList; deleted tabs lose their
|
||||
// history (intended).
|
||||
var liveTempTabs = Tabs.Where(t => t.IsTempTab).ToList();
|
||||
Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t => t.Clone()).ToList();
|
||||
var livePersistentSession = Tabs
|
||||
.Where(t => !t.IsTempTab)
|
||||
.ToDictionary(t => t.Identifier, t => (t.Messages, t.LastSendUnread));
|
||||
|
||||
Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t =>
|
||||
{
|
||||
var clone = t.Clone();
|
||||
if (livePersistentSession.TryGetValue(clone.Identifier, out var live))
|
||||
{
|
||||
clone.Messages = live.Messages;
|
||||
clone.LastSendUnread = live.LastSendUnread;
|
||||
}
|
||||
return clone;
|
||||
}).ToList();
|
||||
Tabs.AddRange(liveTempTabs);
|
||||
|
||||
OverrideStyle = other.OverrideStyle;
|
||||
ChosenStyle = other.ChosenStyle;
|
||||
ChatTabForward = other.ChatTabForward;
|
||||
ChatTabBackward = other.ChatTabBackward;
|
||||
|
||||
@@ -281,15 +371,24 @@ public class Configuration : IPluginConfiguration
|
||||
RetentionLastRunAt = other.RetentionLastRunAt;
|
||||
|
||||
FirstRunCompleted = other.FirstRunCompleted;
|
||||
HellionThemeEnabled = other.HellionThemeEnabled;
|
||||
HellionThemeWindowOpacity = other.HellionThemeWindowOpacity;
|
||||
UseHellionFont = other.UseHellionFont;
|
||||
|
||||
// v1.1.0 theme engine fields
|
||||
Theme = other.Theme;
|
||||
WindowOpacity = other.WindowOpacity;
|
||||
ReduceMotion = other.ReduceMotion;
|
||||
UseCompactDensity = other.UseCompactDensity;
|
||||
|
||||
EnableAutoTellTabs = other.EnableAutoTellTabs;
|
||||
AutoTellTabsLimit = other.AutoTellTabsLimit;
|
||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||
|
||||
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||
SeenPopOutHeaderHint = other.SeenPopOutHeaderHint;
|
||||
AutoTellTabsOpenAsPopout = other.AutoTellTabsOpenAsPopout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +424,11 @@ public class Tab
|
||||
{
|
||||
public string Name = Language.Tab_DefaultName;
|
||||
|
||||
// v1.2.0 — optionaler FontAwesome-Glyph-Name. Null bedeutet:
|
||||
// Default-Mapping aus TabIconMapping greift (basiert auf Tab-Name).
|
||||
// User können hier per Settings → Tabs einen eigenen Glyph setzen.
|
||||
public string? Icon = null;
|
||||
|
||||
[Obsolete("Removed in favor of SelectedChannels")]
|
||||
public Dictionary<ChatType, ChatSource> ChatCodes = new();
|
||||
|
||||
@@ -529,6 +633,20 @@ public class Tab
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aktuelle Anzahl der gespeicherten Messages. Lock-acquire pro Read
|
||||
/// ist OK für 1×/sec Status-Bar-Polling (v1.2.0).
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
LockSlim.Wait(-1);
|
||||
try { return Messages.Count; }
|
||||
finally { LockSlim.Release(); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array copy of the message list for usage outside of main thread
|
||||
/// </summary>
|
||||
@@ -8,7 +8,7 @@ using Dalamud.Bindings.ImGui;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public static class EmoteCache
|
||||
{
|
||||
@@ -35,20 +35,20 @@ public static class EmoteCache
|
||||
public Emote Emote { get; set; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Emote()
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; }
|
||||
public required string Code { get; set; }
|
||||
|
||||
[JsonPropertyName("imageType")]
|
||||
public string ImageType { get; set; }
|
||||
public required string ImageType { get; set; }
|
||||
}
|
||||
|
||||
public enum LoadingState
|
||||
@@ -66,16 +66,29 @@ public static class EmoteCache
|
||||
|
||||
public static string[] SortedCodeArray = [];
|
||||
|
||||
public static async void LoadData()
|
||||
// Plugin-scoped cancellation source for in-flight emote loads. Dispose
|
||||
// cancels every running download/texture-create so the workers don't
|
||||
// touch a torn-down TextureProvider on plugin reload. Replaced with a
|
||||
// fresh source on the next LoadData() call so a re-enable still works.
|
||||
private static CancellationTokenSource Cts = new();
|
||||
internal static CancellationToken Token => Cts.Token;
|
||||
|
||||
public static async Task LoadData()
|
||||
{
|
||||
if (State is not LoadingState.Unloaded)
|
||||
return;
|
||||
|
||||
// Refresh the CTS in case Dispose was called and we're being re-enabled
|
||||
// in the same process (Dalamud /xlplugins toggle).
|
||||
if (Cts.IsCancellationRequested)
|
||||
Cts = new CancellationTokenSource();
|
||||
|
||||
State = LoadingState.Loading;
|
||||
var ct = Cts.Token;
|
||||
try
|
||||
{
|
||||
var global = await Client.GetAsync(GlobalEmotes);
|
||||
var globalList = await global.Content.ReadAsStringAsync();
|
||||
var global = await Client.GetAsync(GlobalEmotes, ct);
|
||||
var globalList = await global.Content.ReadAsStringAsync(ct);
|
||||
|
||||
foreach (var emote in JsonSerializer.Deserialize<Emote[]>(globalList)!)
|
||||
if (!string.IsNullOrEmpty(emote.Code) && !NotWorking.Contains(emote.Code))
|
||||
@@ -84,8 +97,8 @@ public static class EmoteCache
|
||||
var lastId = string.Empty;
|
||||
for (var i = 0; i < 15; i++)
|
||||
{
|
||||
var top = await Client.GetAsync(Top100Emotes.Format(BetterTTV, lastId));
|
||||
var topList = await top.Content.ReadAsStringAsync();
|
||||
var top = await Client.GetAsync(Top100Emotes.Format(BetterTTV, lastId), ct);
|
||||
var topList = await top.Content.ReadAsStringAsync(ct);
|
||||
|
||||
var jsonList = JsonSerializer.Deserialize<List<Top100>>(topList)!;
|
||||
// BetterTTV occasionally returns entries with a null Code; the
|
||||
@@ -103,14 +116,29 @@ public static class EmoteCache
|
||||
SortedCodeArray = Cache.Keys.Order().ToArray();
|
||||
State = LoadingState.Done;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Plugin disposed while the cache was loading; leave State on
|
||||
// Loading so a subsequent re-enable can re-issue LoadData with
|
||||
// a fresh CTS (handled above).
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Reset to Unloaded so a later trigger (e.g. the user reopening
|
||||
// the Emotes tab after the network recovers) can retry. Without
|
||||
// this the State stays on Loading and the early-out at the top
|
||||
// of LoadData blocks every further attempt until plugin reload.
|
||||
State = LoadingState.Unloaded;
|
||||
Plugin.Log.Error(ex, "BetterTTV cache wasn't initialized");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Dispose()
|
||||
{
|
||||
// Cancel in-flight downloads / texture creates so the async-void
|
||||
// Load methods bail out before they touch a disposed TextureProvider.
|
||||
Cts.Cancel();
|
||||
|
||||
foreach (var emote in EmoteImages.Values)
|
||||
emote.InnerDispose();
|
||||
}
|
||||
@@ -166,7 +194,7 @@ public static class EmoteCache
|
||||
ImGui.Image(Texture!.Handle, size);
|
||||
}
|
||||
|
||||
internal async Task<byte[]> LoadAsync(Emote emote)
|
||||
internal async Task<byte[]> LoadAsync(Emote emote, CancellationToken ct)
|
||||
{
|
||||
// BetterTTV-supplied Id and ImageType are interpolated straight
|
||||
// into the filename. HTTPS protects the wire, but a compromised
|
||||
@@ -183,15 +211,15 @@ public static class EmoteCache
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
RawData = await File.ReadAllBytesAsync(filePath);
|
||||
RawData = await File.ReadAllBytesAsync(filePath, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
var content = await new HttpClient().GetAsync(EmotePath.Format(emote.Id));
|
||||
RawData = await content.Content.ReadAsByteArrayAsync();
|
||||
var content = await Client.GetAsync(EmotePath.Format(emote.Id), ct);
|
||||
RawData = await content.Content.ReadAsByteArrayAsync(ct);
|
||||
|
||||
await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
stream.Write(RawData, 0, RawData.Length);
|
||||
await stream.WriteAsync(RawData, ct);
|
||||
}
|
||||
|
||||
return RawData;
|
||||
@@ -204,21 +232,28 @@ public static class EmoteCache
|
||||
{
|
||||
public ImGuiEmote Prepare(Emote emote)
|
||||
{
|
||||
Task.Run(() => Load(emote));
|
||||
var ct = EmoteCache.Token;
|
||||
Task.Run(() => Load(emote, ct), ct);
|
||||
return this;
|
||||
}
|
||||
|
||||
private async void Load(Emote emote)
|
||||
private async void Load(Emote emote, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var image = await LoadAsync(emote);
|
||||
var image = await LoadAsync(emote, ct);
|
||||
if (image.Length <= 0)
|
||||
return;
|
||||
|
||||
Texture = await Plugin.TextureProvider.CreateFromImageAsync(image);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
Texture = await Plugin.TextureProvider.CreateFromImageAsync(image, cancellationToken: ct);
|
||||
IsLoaded = true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Plugin disposed mid-load; the EmoteImages entry is also
|
||||
// being torn down, no extra cleanup needed.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Failed = true;
|
||||
@@ -274,15 +309,16 @@ public static class EmoteCache
|
||||
|
||||
public ImGuiGif Prepare(Emote emote)
|
||||
{
|
||||
Task.Run(() => Load(emote));
|
||||
var ct = EmoteCache.Token;
|
||||
Task.Run(() => Load(emote, ct), ct);
|
||||
return this;
|
||||
}
|
||||
|
||||
private async void Load(Emote emote)
|
||||
private async void Load(Emote emote, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var image = await LoadAsync(emote);
|
||||
var image = await LoadAsync(emote, ct);
|
||||
if (image.Length <= 0)
|
||||
return;
|
||||
|
||||
@@ -294,6 +330,8 @@ public static class EmoteCache
|
||||
var frames = new List<(IDalamudTextureWrap Tex, float Delay)>();
|
||||
foreach (var frame in img.Frames)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var delay = frame.Metadata.GetGifMetadata().FrameDelay / 100f;
|
||||
|
||||
// Follows the same pattern as browsers, anything under 0.02s delay will be rounded up to 0.1s
|
||||
@@ -302,13 +340,21 @@ public static class EmoteCache
|
||||
|
||||
var buffer = new byte[4 * frame.Width * frame.Height];
|
||||
frame.CopyPixelDataTo(buffer);
|
||||
var tex = await Plugin.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(frame.Width, frame.Height), buffer);
|
||||
var tex = await Plugin.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(frame.Width, frame.Height), buffer, cancellationToken: ct);
|
||||
frames.Add((tex, delay));
|
||||
}
|
||||
|
||||
Frames = frames;
|
||||
IsLoaded = true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Plugin disposed mid-load; partial frames are released by
|
||||
// InnerDispose on the next dispose pass.
|
||||
foreach (var f in Frames)
|
||||
f.Texture.Dispose();
|
||||
Frames = [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Failed = true;
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
|
||||
namespace ChatTwo.Export;
|
||||
namespace HellionChat.Export;
|
||||
|
||||
internal enum ExportFormat
|
||||
{
|
||||
@@ -6,7 +6,7 @@ using Dalamud.Interface.ManagedFontAtlas;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public class FontManager
|
||||
{
|
||||
@@ -18,8 +18,6 @@ public class FontManager
|
||||
|
||||
internal IFontHandle FontAwesome = null!;
|
||||
|
||||
internal readonly byte[] GameSymFont;
|
||||
|
||||
private ushort[] Ranges = [];
|
||||
private ushort[] JpRange = [];
|
||||
|
||||
@@ -30,25 +28,6 @@ public class FontManager
|
||||
36f, 40f, 45f, 46f, 68f, 90f,
|
||||
];
|
||||
|
||||
public FontManager()
|
||||
{
|
||||
var filePath = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "FFXIV_Lodestone_SSF.ttf");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
GameSymFont = File.ReadAllBytes(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameSymFont = new HttpClient().GetAsync("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")
|
||||
.Result
|
||||
.Content
|
||||
.ReadAsByteArrayAsync()
|
||||
.Result;
|
||||
|
||||
Dalamud.Utility.FilesystemUtil.WriteAllBytesSafe(filePath, GameSymFont);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Backing bytes for the bundled Hellion font (Exo 2, OFL-1.1). Lazily
|
||||
/// extracted from the assembly's manifest resources on first use; the
|
||||
@@ -141,7 +120,16 @@ public class FontManager
|
||||
e => e.OnPreBuild(
|
||||
tk =>
|
||||
{
|
||||
var config = new SafeFontConfig {SizePt = Plugin.Config.GlobalFontV2.SizePt, GlyphRanges = Ranges};
|
||||
// v1.2.0 — Bei aktiver Hellion-Schrift (Exo 2 ist Variable-Font)
|
||||
// wird die User-Schriftgröße aus FontSizeV2 als SizePt angewendet.
|
||||
// Der Bestand-Pfad nutzt weiter GlobalFontV2.SizePt aus dem
|
||||
// Custom-Font-Stack. Ohne diese Verzweigung war FontSizeV2 bei
|
||||
// UseHellionFont=true wirkungslos, was 4K-User mit größerer
|
||||
// Skalierung blockierte (Settings → Erscheinungsbild → Schriftarten).
|
||||
var basePt = Plugin.Config.UseHellionFont
|
||||
? Plugin.Config.FontSizeV2
|
||||
: Plugin.Config.GlobalFontV2.SizePt;
|
||||
var config = new SafeFontConfig {SizePt = basePt, GlyphRanges = Ranges};
|
||||
config.MergeFont = Plugin.Config.UseHellionFont
|
||||
? tk.AddFontFromMemory(GetHellionFontBytes(), config, "Hellion-Exo2")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
@@ -22,7 +22,7 @@ using Lumina.Text.ReadOnly;
|
||||
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
internal sealed unsafe class Chat : IDisposable
|
||||
{
|
||||
@@ -252,7 +252,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
{
|
||||
playerName = SeString.Parse(agent->TellPlayerName).TextValue;
|
||||
worldId = agent->TellWorldId;
|
||||
Plugin.Log.Debug($"Detected tell target '{playerName}'@{worldId}");
|
||||
Plugin.Log.Debug($"Detected tell target '[redacted]'@{worldId}");
|
||||
}
|
||||
|
||||
Plugin.CurrentTab.CurrentChannel = new UsedChannel
|
||||
@@ -400,7 +400,9 @@ internal sealed unsafe class Chat : IDisposable
|
||||
}
|
||||
|
||||
var idx = RotateLinkshell(currentIndex, rotate, channel == InputChannel.Linkshell1 ? ValidLinkshell : ValidCrossLinkshell);
|
||||
return channel + idx;
|
||||
// RotateLinkshell returns null when no valid linkshell is found within 8 iterations.
|
||||
// Forward the null so the caller can keep the existing channel instead of crashing on nullable arithmetic.
|
||||
return idx is null ? null : channel + idx.Value;
|
||||
}
|
||||
default:
|
||||
return channel;
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Text;
|
||||
using ChatTwo.Resources;
|
||||
using HellionChat.Resources;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
public unsafe class ChatBox
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Util;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
internal sealed unsafe class Context
|
||||
{
|
||||
+23
-3
@@ -16,10 +16,12 @@ using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.AtkValueType;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
internal unsafe class GameFunctions : IDisposable
|
||||
{
|
||||
internal const string NewGamePlusAddonName = "QuestRedo";
|
||||
|
||||
#region Hooks
|
||||
[Signature("E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 48 8B D0 49 8D 4F", DetourName = nameof(ResolveTextCommandPlaceholderDetour))]
|
||||
private Hook<ResolveTextCommandPlaceholderDelegate>? ResolveTextCommandPlaceholderHook = null!;
|
||||
@@ -243,15 +245,33 @@ internal unsafe class GameFunctions : IDisposable
|
||||
vf0(agent, &result, &value, 0, 0);
|
||||
}
|
||||
|
||||
private readonly nint PlaceholderNamePtr = Marshal.AllocHGlobal(128);
|
||||
private const int PlaceholderBufferSize = 128;
|
||||
private readonly nint PlaceholderNamePtr = Marshal.AllocHGlobal(PlaceholderBufferSize);
|
||||
private readonly string Placeholder = $"<{Guid.NewGuid():N}>";
|
||||
private string? ReplacementName;
|
||||
|
||||
private nint ResolveTextCommandPlaceholderDetour(nint a1, byte* placeholderText, byte a3, byte a4)
|
||||
{
|
||||
// The detour is only invoked through the hook, so the hook should
|
||||
// never be null here, but the nullable field declaration forces us
|
||||
// to handle the theoretical race during teardown.
|
||||
if (ResolveTextCommandPlaceholderHook is null)
|
||||
return nint.Zero;
|
||||
|
||||
var placeholder = MemoryHelper.ReadStringNullTerminated((nint) placeholderText);
|
||||
if (ReplacementName == null || placeholder != Placeholder)
|
||||
return ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4);
|
||||
return ResolveTextCommandPlaceholderHook.Original(a1, placeholderText, a3, a4);
|
||||
|
||||
// The fixed buffer is 128 bytes; UTF-8 + null-terminator must fit.
|
||||
// FFXIV player names plus an @World suffix should never approach this
|
||||
// limit, but a malformed ReplacementName must not overflow the buffer.
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(ReplacementName);
|
||||
if (byteCount >= PlaceholderBufferSize)
|
||||
{
|
||||
Plugin.Log.Warning($"Replacement name too long for placeholder buffer ({byteCount} bytes >= {PlaceholderBufferSize}); falling back to original.");
|
||||
ReplacementName = null;
|
||||
return ResolveTextCommandPlaceholderHook.Original(a1, placeholderText, a3, a4);
|
||||
}
|
||||
|
||||
MemoryHelper.WriteString(PlaceholderNamePtr, ReplacementName);
|
||||
ReplacementName = null;
|
||||
+25
-7
@@ -1,16 +1,16 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using ModifierFlag = ChatTwo.GameFunctions.Types.ModifierFlag;
|
||||
using ModifierFlag = HellionChat.GameFunctions.Types.ModifierFlag;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
internal enum KeyboardSource {
|
||||
Game,
|
||||
@@ -414,13 +414,13 @@ internal unsafe class KeybindManager : IDisposable {
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
||||
DispatchTabDelta(1);
|
||||
return;
|
||||
}
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
||||
DispatchTabDelta(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,6 +465,24 @@ internal unsafe class KeybindManager : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
// v0.6.0 — central dispatch for ChatTabForward/Backward. If a pop-out
|
||||
// window currently has its compact input focused, the keybind is
|
||||
// forwarded into that pop-out's ChatInputBar so the user navigates
|
||||
// tabs in the window they are typing in. Otherwise the main window
|
||||
// handles it (= v0.5.x behavior).
|
||||
private void DispatchTabDelta(int delta)
|
||||
{
|
||||
foreach (var popout in Plugin.ChatLogWindow.ActivePopouts)
|
||||
{
|
||||
if (popout.HasFocusedInputBar && popout.InputBar != null)
|
||||
{
|
||||
popout.InputBar.HandleKeybindForward(delta);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(delta);
|
||||
}
|
||||
|
||||
private static Keybind GetKeybind(string id)
|
||||
{
|
||||
var outData = new FFXIVClientStructs.FFXIV.Client.System.Input.Keybind();
|
||||
@@ -1,10 +1,10 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
namespace HellionChat.GameFunctions;
|
||||
|
||||
internal static unsafe class Party
|
||||
{
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
internal class ChannelSwitchInfo {
|
||||
internal InputChannel? Channel { get; }
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
internal sealed class ChatActivatedArgs
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
internal class Keybind
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
[Flags]
|
||||
public enum ModifierFlag
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
internal enum RotateMode
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
internal sealed class TellHistoryInfo
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
public enum TellReason
|
||||
{
|
||||
+5
-2
@@ -1,7 +1,7 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
|
||||
namespace ChatTwo.GameFunctions.Types;
|
||||
namespace HellionChat.GameFunctions.Types;
|
||||
|
||||
[Serializable]
|
||||
public class TellTarget
|
||||
@@ -20,7 +20,7 @@ public class TellTarget
|
||||
}
|
||||
|
||||
public bool IsSet()
|
||||
=> Name.Length > 0 && World > 0;
|
||||
=> !string.IsNullOrEmpty(Name) && World > 0;
|
||||
|
||||
public string ToWorldString()
|
||||
=> Sheets.WorldSheet.TryGetRow(World, out var worldRow) ? worldRow.Name.ToString() : string.Empty;
|
||||
@@ -30,6 +30,9 @@ public class TellTarget
|
||||
|
||||
public unsafe void FromTarget(IPlayerCharacter target)
|
||||
{
|
||||
if (target.Address == nint.Zero)
|
||||
return;
|
||||
|
||||
Name = target.Name.TextValue;
|
||||
World = target.HomeWorld.RowId;
|
||||
ContentId = ((Character*)target.Address)->ContentId;
|
||||
@@ -0,0 +1,98 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<!-- Hellion Chat versioning runs separately from upstream Chat 2.
|
||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
||||
called out in the yaml changelog so users can see what it
|
||||
derives from. -->
|
||||
<Version>1.2.3</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Honor packages.lock.json on restore so floating version ranges
|
||||
don't silently drift between machines or CI runs. -->
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<!-- v1.0.0 standalone cut — both AssemblyName and RootNamespace
|
||||
are HellionChat. The plugin no longer maintains source-level
|
||||
cherry-pick compatibility with upstream Infiziert90/ChatTwo;
|
||||
upstream changes are integrated manually if at all. -->
|
||||
<AssemblyName>HellionChat</AssemblyName>
|
||||
<RootNamespace>HellionChat</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Closed ranges on packages with breaking-change history block a
|
||||
surprise major bump when the lock file is regenerated. The
|
||||
lock file pins the exact version per build; the upper bound
|
||||
keeps the unlock path from drifting across major lines. -->
|
||||
<PackageReference Include="MessagePack" Version="[3.1.4, 4.0.0)" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.7" />
|
||||
<!-- Override the transitively-referenced native SQLite build to one
|
||||
that ships SQLite >= 3.50.3 (CVE-2025-6965 memory corruption,
|
||||
CVE-2025-7709 fixed in 3.50.x). Microsoft.Data.Sqlite 10.0.7
|
||||
pulls SQLitePCLRaw 2.1.11 which carries the older lib; pinning
|
||||
the lib package directly forces the newer native binary
|
||||
without a major bump on the managed wrapper. -->
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
|
||||
<PackageReference Include="morelinq" Version="4.4.0" />
|
||||
<PackageReference Include="Pidgin" Version="[3.5.1, 4.0.0)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="[3.1.12, 4.0.0)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Pure-function test suites in HellionChat.Tests need access to
|
||||
the internal helper classes (StringUtil, UriPayload, Tokenizer
|
||||
etc.). Test assembly does not get redistributed. -->
|
||||
<InternalsVisibleTo Include="HellionChat.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\Language.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Language.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\Language.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Language.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- HellionChat — Hellion-specific resource bundle (HellionStrings.resx
|
||||
+ HellionStrings.<lang>.resx) is picked up automatically by the SDK
|
||||
default include. Designer.cs is hand-maintained, no auto-gen needed. -->
|
||||
|
||||
<!-- Bundled Hellion font (Exo 2, OFL-1.1). Embedded as a manifest
|
||||
resource with a fixed LogicalName so FontManager can pull the
|
||||
bytes back at runtime via AddFontFromMemory. The OFL license
|
||||
text travels with it inside the assembly to satisfy the
|
||||
"license must be distributed with the font" clause. -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\HellionFont.ttf">
|
||||
<LogicalName>HellionFont.ttf</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\HellionFont-OFL.txt">
|
||||
<LogicalName>HellionFont-OFL.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Themes\Builtin\example-theme.json">
|
||||
<LogicalName>HellionChat.Themes.Builtin.example-theme.json</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!-- Plugin icon. Copy images/* into the build output so Dalamud
|
||||
finds the icon next to the DLL, and let the SDK default
|
||||
DalamudPackager pipeline include the same path in the
|
||||
release ZIP. Earlier we shipped a custom DalamudPackager
|
||||
targets override that explicitly set HandleImages and
|
||||
ImagesPath; that override conflicted with the SDK 15
|
||||
default and the resulting manifest carried no IconUrl.
|
||||
Removed in v0.5.2. -->
|
||||
<ItemGroup>
|
||||
<None Include="images\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Executable
+435
@@ -0,0 +1,435 @@
|
||||
name: Hellion Chat
|
||||
author: JonKazama-Hellion
|
||||
punchline: Chat replacement with privacy controls aligned to EU, US and JP rules — based on Chat 2 (EUPL-1.2)
|
||||
description: |-
|
||||
Hellion Chat is a privacy-focused chat replacement for FINAL FANTASY XIV
|
||||
based on the Chat 2 codebase (EUPL-1.2). One feature is intentionally
|
||||
removed (the optional webinterface) and a stack of privacy controls is
|
||||
added on top. Tabs, channel filters, RGB colours, emotes, screenshot
|
||||
mode, IPC integration and the chat replacement window itself work the
|
||||
same. The webinterface is intentionally not part of Hellion Chat because
|
||||
it serves a different use case from the smaller default footprint this
|
||||
plugin is built around.
|
||||
|
||||
On top of that, Hellion Chat adds privacy and data-handling controls
|
||||
designed to align with the modern data protection rules that apply
|
||||
across the EU, the United States and Japan. By default only your own
|
||||
conversations are stored; messages from strangers, NPCs and system
|
||||
spam stay out of the database. Retention windows are configurable per
|
||||
channel, history can be wiped retroactively, and stored data can be
|
||||
exported on demand.
|
||||
|
||||
Key privacy and data-handling features:
|
||||
|
||||
- Channel whitelist with a Privacy-First default
|
||||
- Per-channel retention with a daily background sweep
|
||||
- Retroactive cleanup with a Ctrl+Shift confirm
|
||||
- Export to Markdown, JSON or CSV
|
||||
- First-run wizard with three preset profiles (Privacy-First, Casual,
|
||||
Full History)
|
||||
- Bilingual UI (English and German) with live language switching
|
||||
- Independent plugin state — own config file and database directory,
|
||||
so Hellion Chat does not share state with upstream Chat 2
|
||||
|
||||
v1.1.0 — Theme engine with five built-in themes (Hellion Arctic,
|
||||
Chat 2 Klassik, Event Horizon, Moonlit Bloom, Mint Grove) plus
|
||||
JSON-based custom-theme authoring. Settings rebuilt around a card
|
||||
grid with section detail views. See docs/THEME-AUTHORING.md.
|
||||
|
||||
v1.2.3 — Theme catalogue grown to nine built-in themes:
|
||||
Hellion Arctic, Hellion Spectrum (CVD-safe Deuteran/Protan),
|
||||
Chat 2 Klassik, Event Horizon, Moonlit Bloom, Mint Grove,
|
||||
Night Blue, Indigo Violet, Forge Merchantman.
|
||||
|
||||
Based on Chat 2 by Infi and Anna, licensed under EUPL-1.2.
|
||||
|
||||
Modding & support: join the Hellion Forge Discord at
|
||||
https://discord.gg/X9V7Kcv5gR — community for Hellion Chat and
|
||||
other Hellion Online Media plugins/tools.
|
||||
repo_url: https://github.com/JonKazama-Hellion/HellionChat
|
||||
accepts_feedback: true
|
||||
icon_url: https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/icon.png
|
||||
image_urls:
|
||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/chatWindow.png
|
||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/settingsOverview.png
|
||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/HellionChat/images/themesPicker.png
|
||||
tags:
|
||||
- Social
|
||||
- UI
|
||||
- Chat
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 1.2.3 — Theme Expansion**
|
||||
|
||||
Four new built-in themes round out the picker. No engine changes,
|
||||
no settings touched — just more colour options.
|
||||
|
||||
- **Night Blue** — Royal Blue on deep marine. Cool tech-dashboard
|
||||
mood, distinct from the brand themes.
|
||||
- **Indigo Violet** — Royal Violet on deep indigo with a turquoise-
|
||||
mint counter for an aurora glitter feel. Sister to Event Horizon
|
||||
but darker and denser; the turquoise accent keeps the two
|
||||
distinguishable.
|
||||
- **Forge Merchantman** — Patina bronze on workshop slate, warm
|
||||
amber counter. Hellion Forge given a theme of its own — sister
|
||||
to Hellion Arctic but greener and warmer instead of cold cyan.
|
||||
- **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) is preserved;
|
||||
tones are chosen so each channel stays distinguishable under
|
||||
red-green colour vision deficiency. Covers the ~99% of CVD cases
|
||||
that are red-green.
|
||||
|
||||
No schema bump, no migration. Default theme is unchanged (Hellion
|
||||
Arctic). Existing custom themes keep working.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.2.1 — Settings Cleanup**
|
||||
|
||||
Re-sorted the settings menu so related options live together. Card names
|
||||
now describe their contents in plain words — "Theme & Layout", "Fonts &
|
||||
Colours", "Data Management" — and each card has a short subtitle so you
|
||||
don't have to guess where a setting lives. No new features, just
|
||||
housekeeping.
|
||||
|
||||
Card changes:
|
||||
|
||||
- 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) is the new home for font choice, font size and
|
||||
per-channel chat colours.
|
||||
- Data Management (new) is everything you do with stored messages:
|
||||
retention windows, cleanup, export, the database viewer and the
|
||||
advanced shift-click tools. All previously scattered between Privacy
|
||||
and Database.
|
||||
- Privacy is now focused on one job: the privacy filter.
|
||||
- Chat absorbs the Auto-Tell-Tabs history preload slider that used to
|
||||
live under Privacy.
|
||||
- General groups the keybind mode under Input where it belongs.
|
||||
|
||||
Cleanup:
|
||||
|
||||
- Removed legacy "Style override" option and the unused style-name field
|
||||
— both made obsolete by the Themes system in 1.1.0.
|
||||
- Removed the legacy WindowAlpha slider; if you had it set, the value is
|
||||
automatically migrated to Theme & Layout → Window Style → Window
|
||||
Transparency.
|
||||
- Removed the unused ShowThemeQuickPicker schema field.
|
||||
|
||||
Migration v15 → v16:
|
||||
|
||||
- A backup of your previous config is written to
|
||||
pluginConfigs/HellionChat.json.pre-v16-backup before the schema change,
|
||||
in case you want to roll back manually.
|
||||
- All other settings are preserved unchanged.
|
||||
- One-time toast on first start if you previously had Style override
|
||||
enabled — it explains the change. Users who never touched that setting
|
||||
see no toast.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.2.0 — Layout Refresh**
|
||||
|
||||
Second UI cycle: tab layouts modernised in both modes, a new
|
||||
bottom status bar, card-rows as default message render, and
|
||||
Auto-Tell tabs that you can finally tell apart at a glance.
|
||||
|
||||
Sidebar (icon-only, fixed 44 px):
|
||||
|
||||
- Tab name on hover-tooltip, vertical accent pill on the
|
||||
active tab, child background no longer paints the top
|
||||
padding area.
|
||||
- Per-tab custom icons via Settings → Tabs.
|
||||
- Auto-Tell tabs: each partner gets a hashed icon (envelope/
|
||||
star/heart/bell/bookmark/flag/fire) plus hashed color
|
||||
(12-color palette) — 84 distinct combinations.
|
||||
- Pulsing red dot in the top-right of any tab with unread
|
||||
messages, subtle 2-second sine pulse, respects
|
||||
Configuration.ReduceMotion.
|
||||
|
||||
Top tabs:
|
||||
|
||||
- Accent underline pill on the active tab instead of the old
|
||||
background fill. Icon prefixes were attempted but reverted
|
||||
— Dalamud's default font atlas has no FontAwesome glyphs.
|
||||
|
||||
Bottom status bar (22 px, 1×/sec cached):
|
||||
|
||||
- Active channel with color dot, Privacy-First badge, tab +
|
||||
message counters, auto-tell counter (hidden at zero),
|
||||
plugin version (right-aligned, muted).
|
||||
|
||||
Message rendering:
|
||||
|
||||
- Card rows by default — sender header in channel color, body
|
||||
on its own line, subtle border between cards.
|
||||
- Compact-Density toggle in Appearance returns the classic
|
||||
single-line `[HH:mm] Sender: Text` layout.
|
||||
|
||||
Bug fixes from in-game testing:
|
||||
|
||||
- Settings save no longer wipes chat history. Refilter cycle
|
||||
only runs when filter-relevant settings actually changed
|
||||
(privacy, channel selection); cosmetic changes leave the
|
||||
chat intact. Persistent and Auto-Tell tabs both survive.
|
||||
- Hellion Schrift (Exo 2) no longer blocks font-size
|
||||
adjustment — 4K users can scale up properly.
|
||||
- Sidebar buttons align with the first message row, status
|
||||
bar version slot is no longer clipped.
|
||||
|
||||
Migration v14 → v15: legacy theme fields removed
|
||||
(HellionThemeEnabled, HellionThemeWindowOpacity). All other
|
||||
settings preserved.
|
||||
|
||||
Polish (lerps, theme crossfade, header quick-picker) follows
|
||||
in v1.3.0.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.1.0 — Theme Foundation**
|
||||
|
||||
First major UI cycle after the standalone v1.0.0 cut. Theme engine,
|
||||
five built-in themes, customisable JSON themes, modernised settings
|
||||
layout.
|
||||
|
||||
New themes (Settings → Themes):
|
||||
|
||||
- **Hellion Arctic** — the brand default, Arctic Cyan + Ember Glow
|
||||
on industrial slate.
|
||||
- **Chat 2 Klassik** — Steel Blue on neutral grey, eckige Kanten.
|
||||
The upstream Chat 2 look on the new engine.
|
||||
- **Event Horizon** — Cosmic Purple on near-black. Deep-space mood.
|
||||
- **Moonlit Bloom** — Bloom Magenta + Soft Sage on deep-violet
|
||||
night.
|
||||
- **Mint Grove** — Mint Green + Honey Amber on deep forest. First
|
||||
member of the Grove family.
|
||||
|
||||
Theme engine highlights:
|
||||
|
||||
- Slug-based selection in Settings → Themes with mini-mockup
|
||||
previews per theme.
|
||||
- Click a theme card and the whole plugin (chat, settings,
|
||||
pop-outs, viewer) repaints instantly.
|
||||
- Custom themes via JSON in pluginConfigs/HellionChat/themes/.
|
||||
Example template seeded on first launch.
|
||||
- Optional per-theme chat-channel colours. When a theme proposes
|
||||
its own chat colours and yours differ, a dezent banner offers
|
||||
Apply / Keep — never auto-overwriting.
|
||||
- Migration v13 → v14: existing users land on Hellion Arctic. Pick
|
||||
Chat 2 Klassik to keep the upstream look.
|
||||
|
||||
Settings layout:
|
||||
|
||||
- New card-grid overview on Settings open. Click a card to drill
|
||||
into the section.
|
||||
- Breadcrumb back to overview, ESC also returns.
|
||||
- Detail view drops the redundant tab list — section content uses
|
||||
the full width.
|
||||
|
||||
Branding:
|
||||
|
||||
- Plugin icon swapped from the ChatTwo derivative to the Hellion
|
||||
Forge hammer.
|
||||
- New docs/THEME-AUTHORING.md walks you through writing your own
|
||||
themes with the Forge logo on top.
|
||||
|
||||
Technical:
|
||||
|
||||
- HellionStyle.PushGlobal is now theme-driven. Configuration.
|
||||
HellionThemeEnabled is deprecated and will be removed in v1.2.0.
|
||||
- New ThemeRegistry singleton with LastWriteTime-cached custom-
|
||||
theme loader.
|
||||
- 51 local unit tests cover the data model, registry, JSON round-
|
||||
trip and built-in sanity checks.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.0.3 — Polish patch**
|
||||
|
||||
- New: optionally hide chat (and every other plugin window) while the
|
||||
New Game+ menu is open. Toggle in Settings → Window → Frame, default
|
||||
off. Closing the menu restores all windows.
|
||||
- New: optionally tint the channel selector button next to the input
|
||||
field with the currently active channel's colour. Toggle in
|
||||
Settings → Appearance → Colours, default on. Matches the existing
|
||||
input-text tint and respects ExtraChat overrides.
|
||||
- Fix: status, item and other inline hover icons keep their original
|
||||
aspect ratio. Debuff icons with non-square dimensions are no longer
|
||||
visually squished into a 32×32 box.
|
||||
- Diagnostic: hide-state transitions (battle, cutscene, user-hide,
|
||||
cutscene override) are now logged on Verbose level for easier bug
|
||||
reports — off by default, enable with `/xllog set HellionChat verbose`.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.0.1 — Window Position Recovery**
|
||||
|
||||
- Automatic bounds check on the first draw after plugin load.
|
||||
When the persisted window position has no overlap with the
|
||||
primary viewport, the window snaps to a safe top-left default.
|
||||
Helpful after a monitor disconnect, resolution change or
|
||||
multi-monitor layout switch between sessions.
|
||||
- New "Reset Window Position" button in Settings → Window → Frame
|
||||
as a manual escape hatch for edge cases the automatic check
|
||||
doesn't catch.
|
||||
|
||||
Tested on Linux/Wayland with a hard-cut three-monitor reduction;
|
||||
window recovers cleanly without manual JSON editing.
|
||||
|
||||
Housekeeping carried over since v1.0.0:
|
||||
|
||||
- Documentation restructured into docs/ folder. New CHANGELOG,
|
||||
CONTRIBUTORS, LEARNING-JOURNEY and ROADMAP added
|
||||
- Stale ChatTwo/* paths in repo configs updated to HellionChat/*
|
||||
- Pidgin parser library bumped from 3.3.0 to 3.5.1 (CIString
|
||||
Unicode fix relevant for non-ASCII channel/tab names)
|
||||
- GitHub Actions: actions/setup-dotnet bumped 4 → 5,
|
||||
github/codeql-action bumped 3 → 4
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 1.0.0 — Standalone Major Release**
|
||||
|
||||
First fully standalone release. Internal cleanup plus a sweep of
|
||||
pre-existing correctness, security, threading and resource-leak
|
||||
fixes carried over from the upstream codebase. No user action
|
||||
required — auto-update applies cleanly, configuration and database
|
||||
paths unchanged.
|
||||
|
||||
Standalone identity:
|
||||
|
||||
- Code namespace consolidated from ChatTwo.* to HellionChat.* across
|
||||
all source files
|
||||
- IPC channels migrated from ChatTwo.* to HellionChat.* (6 channels:
|
||||
Register, Available, Unregister, Invoke, GetChatInputState,
|
||||
ChatInputStateChanged) — third-party plugins that bound to the old
|
||||
channels need to be updated; none known at release time
|
||||
- ImGui popup ID renamed to hellionchat-context-popup
|
||||
- Repository folder restructured (ChatTwo/ → HellionChat/), all CI
|
||||
and build paths updated accordingly
|
||||
- Public-facing descriptions reworded from upstream-fork framing to
|
||||
standalone framing (Chat 2 attribution preserved per EUPL-1.2)
|
||||
- Colour preset 'ChatTwo Default' is now 'Klassik (Chat 2 Default)'
|
||||
|
||||
Safety:
|
||||
|
||||
- Plugin now refuses to load when upstream Chat 2 is also active —
|
||||
bilingual conflict message in EN/DE, throw before any subsystem
|
||||
initialization, prevents the runtime crash that previously occurred
|
||||
when both plugins replaced the same chat window in parallel
|
||||
- SQLite native binary bumped to 3.50.3 (CVE-2025-6965 memory
|
||||
corruption from aggregate-term overflow, CVE-2025-7709)
|
||||
- NuGet restore now honors packages.lock.json so transitive
|
||||
dependencies don't drift between machines or CI runs
|
||||
|
||||
Default tab layout sharpened (one-time tab reset on first start):
|
||||
|
||||
The first-run tab layout is reorganized into five thematic tabs
|
||||
based on external tester feedback. General contains only Say,
|
||||
Yell and Shout (immediate-surroundings public chat). System
|
||||
absorbs the gameplay-event streams (NpcDialogue, Loot, Crafting,
|
||||
Gathering, PF recruitment pings) and announcement noise
|
||||
(BattleSystem, FreeCompanyAnnouncement, PvpTeamAnnouncement)
|
||||
that previously lived in General. FreeCompany, Group and
|
||||
Linkshell each own their channel set. The static Tell tab is
|
||||
gone — Auto-Tell-Tabs spawns per-conversation tabs on demand.
|
||||
The Beginner / Novice-Network preset is no longer added by
|
||||
default but is still available via Settings, Tabs.
|
||||
|
||||
This is a one-time tab-layout reset for users on config version
|
||||
12 or older. Privacy, Retention, Theme and every other setting
|
||||
is preserved. Your previous tab configuration is written to
|
||||
pluginConfigs/HellionChat.json.pre-v13-backup so you can restore
|
||||
it manually if you prefer the old layout.
|
||||
|
||||
Crash-class fixes (formerly latent in upstream):
|
||||
|
||||
- MathUtil.HasOverlap now uses a correct AABB test; identical or
|
||||
edge-touching rectangles are no longer reported as non-overlapping
|
||||
- ChatCode.Equals compares fields directly instead of GetHashCode;
|
||||
removes the hash-collision anti-pattern
|
||||
- IpcManager.Dispose uses UnregisterAction to match the matching
|
||||
RegisterAction call; previous mismatch leaked the action
|
||||
subscription on every plugin reload
|
||||
- ExtraChat.Dispose now unsubscribes all three IPC subscriptions
|
||||
(was only the first); leaks closed
|
||||
- TellTarget.FromTarget guards against a zero IPlayerCharacter.Address
|
||||
before dereferencing the unsafe Character* cast
|
||||
- GameFunctions ResolveTextCommandPlaceholderDetour null-checks the
|
||||
Hook reference instead of using the null-forgiving operator
|
||||
- Popout.cs and SettingsTabs/Tabs.cs bounds-check list indexing so
|
||||
a tab drop or empty-worlds list no longer crashes the UI
|
||||
- Debugger.cs now declares IDisposable so the existing Dispose runs
|
||||
|
||||
Correctness fixes:
|
||||
|
||||
- GlobalParametersCache.GetValue captures Cache into a local before
|
||||
the bounds check, so a concurrent Refresh can't slip a different
|
||||
array between check and read
|
||||
- IconUtil binary search bounds initialized to entries.Length-1 and
|
||||
reset on redirect-restart; entries.Length==0 short-circuits
|
||||
- Sheets.WorldsOnDatacenter now compares DataCenter.RowId (was
|
||||
Region.RowId) so it actually returns same-DC worlds
|
||||
- Message.cs back-reference loop iterates the processed Sender/Content
|
||||
properties so chunks added by CheckMessageContent get Message set
|
||||
- Language.zh-Hans Webinterface_Start_Success corrected to
|
||||
"网页界面已启动" (was "网页界面已停止")
|
||||
|
||||
Threading and async:
|
||||
|
||||
- AutoTranslate Entries/ValidEntries are now serialized behind a
|
||||
single lock; the preload worker thread and main thread no longer
|
||||
race on the underlying dictionary/hash set
|
||||
- Privacy retention and cleanup workers bound their framework-refresh
|
||||
waits to 5 seconds with a logged timeout; a hung framework tick can
|
||||
no longer deadlock the background worker
|
||||
|
||||
Resource handling:
|
||||
|
||||
- EmoteCache reuses the static HttpClient instead of allocating a new
|
||||
one per call (closed socket leak)
|
||||
- FontManager wraps HttpClient/HttpResponseMessage in using-blocks
|
||||
and adds EnsureSuccessStatusCode; failed downloads no longer
|
||||
silently produce a zero-byte font file
|
||||
- SearchSelector mixes the row index into the ImGui ID stack so
|
||||
selectables don't collapse to a single ambiguous ID
|
||||
- SettingsTabs/Chat blocked-emote add-button now opens its selector
|
||||
popup on left-click
|
||||
|
||||
Performance:
|
||||
|
||||
- DbViewer text export caches filteredHistory.Count once instead of
|
||||
re-enumerating the IEnumerable on every batch (O(N) instead of
|
||||
O(N²) on large histories)
|
||||
|
||||
License attribution (NOTICE.md, COPYRIGHT, THIRD_PARTY_NOTICES.md
|
||||
and the Credits section in README) is unchanged.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.6.1 — Pop-Out Discoverability & /tell Auto-Pop-Out**
|
||||
|
||||
- Pop-out button now visible in the chat header (no more hunting
|
||||
through the right-click menu)
|
||||
- One-time hint banner explains pop-out tabs and the right-click
|
||||
shortcut
|
||||
- New setting: open new /tell tabs directly as pop-out windows
|
||||
(Settings → Chat → Auto-Tell-Tabs)
|
||||
- Pop-out input is now enabled by default — closing a pop-out still
|
||||
returns the tab to the sidebar
|
||||
- Bugfix: dropping or logging out with an LRU/popped auto-tell tab
|
||||
now also closes its pop-out window (no more ghost windows)
|
||||
- Bugfix: dead zone below the chat input bar when the v0.6.0 pop-out
|
||||
hint banner was visible (also fixed retroactively for the v0.6.0
|
||||
banner inside pop-outs)
|
||||
|
||||
Modding & support: join Hellion Forge — https://discord.gg/X9V7Kcv5gR
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
Earlier history: https://github.com/JonKazama-Hellion/HellionChat/releases
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Hellion Chat — v0.6.0 shared input history. Replaces the embedded
|
||||
// ChatLogWindow.InputBacklog so that pop-out windows with their own
|
||||
// ChatInputBar can navigate the same Up/Down history as the main window.
|
||||
// Index semantics are kept identical to the v0.5.x InputBacklog:
|
||||
// index 0 = oldest entry
|
||||
// index Count - 1 = newest entry
|
||||
// Push performs move-to-newest deduplication: existing entries are
|
||||
// removed before the new one is appended at the end.
|
||||
public static class InputHistoryService
|
||||
{
|
||||
private const int MaxSize = 30;
|
||||
private static readonly List<string> _entries = new();
|
||||
|
||||
public static IReadOnlyList<string> Entries => _entries;
|
||||
|
||||
public static int Count => _entries.Count;
|
||||
|
||||
public static void Push(string entry)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry))
|
||||
return;
|
||||
|
||||
var trimmed = entry.Trim();
|
||||
|
||||
// Move-to-newest: existing entries are removed before the append
|
||||
// so the same line typed twice does not occupy two history slots.
|
||||
for (var i = 0; i < _entries.Count; i++)
|
||||
{
|
||||
if (_entries[i] == trimmed)
|
||||
{
|
||||
_entries.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_entries.Add(trimmed);
|
||||
if (_entries.Count > MaxSize)
|
||||
_entries.RemoveAt(0);
|
||||
}
|
||||
|
||||
public static string? GetByCursor(int cursor)
|
||||
{
|
||||
if (cursor < 0 || cursor >= _entries.Count)
|
||||
return null;
|
||||
return _entries[cursor];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Plugin.Ipc;
|
||||
|
||||
namespace ChatTwo.Ipc;
|
||||
namespace HellionChat.Ipc;
|
||||
|
||||
public sealed class ExtraChat : IDisposable
|
||||
{
|
||||
@@ -20,10 +20,14 @@ public sealed class ExtraChat : IDisposable
|
||||
|
||||
internal (string, uint)? ChannelOverride { get; set; }
|
||||
|
||||
private Dictionary<string, uint> ChannelCommandColoursInternal { get; set; } = new();
|
||||
// Volatile reference: IPC callbacks (OnChannelCommandColours/OnChannelNames) fire on a
|
||||
// Dalamud-dispatcher thread while the ImGui thread reads the IReadOnlyDictionary projections.
|
||||
// Reference assignment is atomic on x64, but the JIT (especially Mono on Wine/Linux) needs
|
||||
// the volatile barrier to guarantee visibility across threads. See AUDIT-2026-05-05 [SEC-01].
|
||||
private volatile Dictionary<string, uint> ChannelCommandColoursInternal = new();
|
||||
internal IReadOnlyDictionary<string, uint> ChannelCommandColours => ChannelCommandColoursInternal;
|
||||
|
||||
private Dictionary<Guid, string> ChannelNamesInternal { get; set; } = new();
|
||||
private volatile Dictionary<Guid, string> ChannelNamesInternal = new();
|
||||
internal IReadOnlyDictionary<Guid, string> ChannelNames => ChannelNamesInternal;
|
||||
|
||||
internal ExtraChat()
|
||||
@@ -40,15 +44,18 @@ public sealed class ExtraChat : IDisposable
|
||||
ChannelCommandColoursInternal = ChannelCommandColoursGate.InvokeFunc(null!);
|
||||
ChannelNamesInternal = ChannelNamesGate.InvokeFunc(null!);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// no-op
|
||||
// ExtraChat is optional; missing IPC peer is normal when the plugin isn't loaded.
|
||||
Plugin.Log.Verbose(ex, "ExtraChat IPC initial state query failed (peer not loaded?)");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
OverrideChannelGate.Unsubscribe(OnOverrideChannel);
|
||||
ChannelCommandColoursGate.Unsubscribe(OnChannelCommandColours);
|
||||
ChannelNamesGate.Unsubscribe(OnChannelNames);
|
||||
}
|
||||
|
||||
private void OnOverrideChannel(OverrideInfo info)
|
||||
@@ -1,7 +1,7 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
|
||||
namespace ChatTwo.Ipc;
|
||||
namespace HellionChat.Ipc;
|
||||
|
||||
using ChatInputState = (bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType);
|
||||
|
||||
@@ -19,8 +19,8 @@ internal sealed class TypingIpc : IDisposable
|
||||
{
|
||||
Plugin = plugin;
|
||||
|
||||
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>("ChatTwo.GetChatInputState");
|
||||
StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>("ChatTwo.ChatInputStateChanged");
|
||||
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>("HellionChat.GetChatInputState");
|
||||
StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>("HellionChat.ChatInputStateChanged");
|
||||
|
||||
StateQueryGate.RegisterFunc(GetState);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
internal sealed class IpcManager : IDisposable
|
||||
{
|
||||
@@ -15,15 +15,15 @@ internal sealed class IpcManager : IDisposable
|
||||
|
||||
public IpcManager()
|
||||
{
|
||||
RegisterGate = Plugin.Interface.GetIpcProvider<string>("ChatTwo.Register");
|
||||
RegisterGate = Plugin.Interface.GetIpcProvider<string>("HellionChat.Register");
|
||||
RegisterGate.RegisterFunc(Register);
|
||||
|
||||
AvailableGate = Plugin.Interface.GetIpcProvider<object?>("ChatTwo.Available");
|
||||
AvailableGate = Plugin.Interface.GetIpcProvider<object?>("HellionChat.Available");
|
||||
|
||||
UnregisterGate = Plugin.Interface.GetIpcProvider<string, object?>("ChatTwo.Unregister");
|
||||
UnregisterGate = Plugin.Interface.GetIpcProvider<string, object?>("HellionChat.Unregister");
|
||||
UnregisterGate.RegisterAction(Unregister);
|
||||
|
||||
InvokeGate = Plugin.Interface.GetIpcProvider<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?>("ChatTwo.Invoke");
|
||||
InvokeGate = Plugin.Interface.GetIpcProvider<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?>("HellionChat.Invoke");
|
||||
|
||||
AvailableGate.SendMessage();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ internal sealed class IpcManager : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnregisterGate.UnregisterFunc();
|
||||
UnregisterGate.UnregisterAction();
|
||||
RegisterGate.UnregisterFunc();
|
||||
Registered.Clear();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -8,7 +8,7 @@ using Dalamud.Game.Text;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public partial class Message
|
||||
{
|
||||
@@ -49,7 +49,10 @@ public partial class Message
|
||||
ExtraChatChannel = extraChatChannel;
|
||||
Hash = GenerateHash();
|
||||
|
||||
foreach (var chunk in sender.Concat(content))
|
||||
// Iterate the processed Content list (returned by CheckMessageContent)
|
||||
// rather than the raw constructor parameter — chunks added or replaced
|
||||
// by CheckMessageContent must also have their back-reference set.
|
||||
foreach (var chunk in Sender.Concat(Content))
|
||||
chunk.Message = this;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Chat;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
@@ -15,7 +15,7 @@ using Lumina.Text.Expressions;
|
||||
using Lumina.Text.Payloads;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
internal class MessageManager : IAsyncDisposable
|
||||
{
|
||||
@@ -34,7 +34,10 @@ internal class MessageManager : IAsyncDisposable
|
||||
// After that, the message is enqueued in the PendingAsync queue, which will
|
||||
// be consumed in a separate thread and perform more processing (emotes,
|
||||
// URLs) as well as inserting the message into the database.
|
||||
private Queue<PendingMessage> PendingSync { get; } = [];
|
||||
// LinkedList instead of Queue: ContentIdResolver hits PendingSync.Last
|
||||
// every hook call. Queue<T>.Last() is the LINQ extension and walks the
|
||||
// whole queue (O(n)); LinkedList<T>.Last is an O(1) node reference.
|
||||
private LinkedList<PendingMessage> PendingSync { get; } = [];
|
||||
private ConcurrentQueue<PendingMessage> PendingAsync { get; } = [];
|
||||
private readonly Thread PendingMessageThread;
|
||||
private readonly CancellationTokenSource PendingThreadCancellationToken = new();
|
||||
@@ -93,6 +96,10 @@ internal class MessageManager : IAsyncDisposable
|
||||
Plugin.Log.Debug("Sleeping because PendingMessageThread thread still alive");
|
||||
}
|
||||
|
||||
// CancellationTokenSource owns an unmanaged WaitHandle; dispose after the
|
||||
// worker thread has drained, otherwise it leaks across plugin reloads.
|
||||
PendingThreadCancellationToken.Dispose();
|
||||
|
||||
Store.Dispose();
|
||||
}
|
||||
|
||||
@@ -113,8 +120,11 @@ internal class MessageManager : IAsyncDisposable
|
||||
LastContentId = contentId;
|
||||
|
||||
// Drain the PendingSync queue into the PendingAsync queue.
|
||||
while (PendingSync.TryDequeue(out var pending))
|
||||
PendingAsync.Enqueue(pending);
|
||||
while (PendingSync.First is { } first)
|
||||
{
|
||||
PendingSync.RemoveFirst();
|
||||
PendingAsync.Enqueue(first.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingMessages(CancellationToken token)
|
||||
@@ -141,7 +151,13 @@ internal class MessageManager : IAsyncDisposable
|
||||
|
||||
internal void ClearAllTabs()
|
||||
{
|
||||
foreach (var tab in Plugin.Config.Tabs)
|
||||
// Hellion Chat — TempTabs haben keine DB-Persistenz (session-only,
|
||||
// direkt vom AutoTellTabsService befüllt). Ein Clear+Refilter würde
|
||||
// sie leer hinterlassen weil FilterAllTabs nichts aus der DB
|
||||
// findet — Tells sind oft durch Privacy-Filter blockiert oder
|
||||
// schlicht session-flüchtig. TempTabs vom Clear-Pfad ausschließen
|
||||
// damit Settings-Save den Tell-Verlauf nicht zerstört.
|
||||
foreach (var tab in Plugin.Config.Tabs.Where(t => !t.IsTempTab))
|
||||
tab.Clear();
|
||||
}
|
||||
|
||||
@@ -155,7 +171,11 @@ internal class MessageManager : IAsyncDisposable
|
||||
|
||||
// We store the pending messages to be added to the chat log in a
|
||||
// temporary list, and apply them all at once after filtering.
|
||||
var pendingTabs = Plugin.Config.Tabs.Select(tab => (tab, new List<Message>())).ToList();
|
||||
// TempTabs werden ausgeschlossen — sie bleiben live-state aus dem
|
||||
// AutoTellTabsService, ein DB-Refilter würde sie nur partial
|
||||
// wiederherstellen falls Tells in DB liegen, oder leer lassen wenn
|
||||
// Privacy-Filter sie blockiert hat.
|
||||
var pendingTabs = Plugin.Config.Tabs.Where(t => !t.IsTempTab).Select(tab => (tab, new List<Message>())).ToList();
|
||||
foreach (var message in messages)
|
||||
foreach (var (_, pendingMessages) in pendingTabs.Where(ptab => ptab.Item1.Matches(message)))
|
||||
pendingMessages.Add(message);
|
||||
@@ -219,7 +239,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
// We delay messages to be handed off to the async processing thread
|
||||
// in the next tick, otherwise we can't get the content ID from the hook
|
||||
// below.
|
||||
PendingSync.Enqueue(pendingMessage);
|
||||
PendingSync.AddLast(pendingMessage);
|
||||
}
|
||||
|
||||
// This hook is called immediately after receiving a message with the
|
||||
@@ -231,11 +251,11 @@ internal class MessageManager : IAsyncDisposable
|
||||
try
|
||||
{
|
||||
ContentIdResolverHook?.Original(agent, contentId, accountId, messageIndex, worldId, chatType);
|
||||
if (PendingSync.Count == 0)
|
||||
if (PendingSync.Last is not { } last)
|
||||
return;
|
||||
|
||||
PendingSync.Last().ContentId = contentId;
|
||||
PendingSync.Last().AccountId = accountId;
|
||||
last.Value.ContentId = contentId;
|
||||
last.Value.AccountId = accountId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Data.Common;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using MessagePack;
|
||||
using MessagePack.Formatters;
|
||||
@@ -13,7 +13,7 @@ using Microsoft.Data.Sqlite;
|
||||
using DalamudUtil = Dalamud.Utility.Util;
|
||||
using Encoding = System.Text.Encoding;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
internal static class DbExtensions
|
||||
{
|
||||
@@ -239,6 +239,9 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private bool ColumnExists(string table, string column)
|
||||
{
|
||||
// PRAGMA does not accept SQLite parameter bindings. The table name is
|
||||
// a compile-time constant fed in from internal call sites, so the
|
||||
// interpolation cannot be reached from any user-controlled path.
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = $"PRAGMA table_info({table});";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
@@ -298,8 +301,10 @@ internal class MessageStore : IDisposable
|
||||
{
|
||||
Plugin.Log.Information($"Setting version {version}");
|
||||
using var cmd = Connection.CreateCommand();
|
||||
// Parameters aren't supported for PRAGMA queries, and you can't set the
|
||||
// version with a pragma_ function.
|
||||
// PRAGMA does not accept SQLite parameter bindings, and there is no
|
||||
// pragma_ function variant that can set the version either. The
|
||||
// version is a compile-time int from the migration sequence, never
|
||||
// user input.
|
||||
cmd.CommandText = $"PRAGMA user_version = {version};";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -346,31 +351,44 @@ internal class MessageStore : IDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(chatTypeDaysMap), "Negative retention is not allowed.");
|
||||
|
||||
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var clauses = new List<string>();
|
||||
foreach (var (type, days) in chatTypeDaysMap)
|
||||
{
|
||||
var cutoff = nowMs - days * 86400000L;
|
||||
clauses.Add($"(ChatType = {type} AND Date < {cutoff})");
|
||||
}
|
||||
|
||||
// Catch-all for channels without an explicit override. "0" is treated
|
||||
// as "do not delete by default" — without an explicit user override,
|
||||
// unmapped channels stay forever instead of getting wiped immediately.
|
||||
if (defaultDays > 0)
|
||||
{
|
||||
var cutoff = nowMs - defaultDays * 86400000L;
|
||||
var explicitTypes = chatTypeDaysMap.Count > 0
|
||||
? string.Join(",", chatTypeDaysMap.Keys)
|
||||
: "-1"; // empty list would produce invalid SQL
|
||||
clauses.Add($"(ChatType NOT IN ({explicitTypes}) AND Date < {cutoff})");
|
||||
}
|
||||
|
||||
if (clauses.Count == 0)
|
||||
if (chatTypeDaysMap.Count == 0 && defaultDays <= 0)
|
||||
return 0;
|
||||
|
||||
long deleted;
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var (type, days) in chatTypeDaysMap)
|
||||
{
|
||||
var cutoff = nowMs - days * 86400000L;
|
||||
var typeParam = $"$type{index}";
|
||||
var cutoffParam = $"$cutoff{index}";
|
||||
cmd.Parameters.AddWithValue(typeParam, type);
|
||||
cmd.Parameters.AddWithValue(cutoffParam, cutoff);
|
||||
clauses.Add($"(ChatType = {typeParam} AND Date < {cutoffParam})");
|
||||
index++;
|
||||
}
|
||||
|
||||
// Catch-all for channels without an explicit override. "0" is
|
||||
// treated as "do not delete by default" — without an explicit
|
||||
// user override, unmapped channels stay forever instead of
|
||||
// getting wiped immediately.
|
||||
if (defaultDays > 0)
|
||||
{
|
||||
var defaultCutoff = nowMs - defaultDays * 86400000L;
|
||||
cmd.Parameters.AddWithValue("$defaultCutoff", defaultCutoff);
|
||||
|
||||
var explicitPlaceholders = chatTypeDaysMap.Count > 0
|
||||
? BindIntList(cmd, "explicit", chatTypeDaysMap.Keys)
|
||||
: "-1"; // empty list would produce invalid SQL
|
||||
clauses.Add($"(ChatType NOT IN ({explicitPlaceholders}) AND Date < $defaultCutoff)");
|
||||
}
|
||||
|
||||
if (clauses.Count == 0)
|
||||
return 0;
|
||||
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE {string.Join(" OR ", clauses)};";
|
||||
cmd.CommandTimeout = 600;
|
||||
deleted = cmd.ExecuteNonQuery();
|
||||
@@ -395,11 +413,11 @@ internal class MessageStore : IDisposable
|
||||
throw new InvalidOperationException("CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe.");
|
||||
}
|
||||
|
||||
var inList = string.Join(",", allowedTypes);
|
||||
long deleted;
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({inList});";
|
||||
var placeholders = BindIntList(cmd, "ct", allowedTypes);
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({placeholders});";
|
||||
cmd.CommandTimeout = 600;
|
||||
deleted = cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -434,7 +452,10 @@ internal class MessageStore : IDisposable
|
||||
// covers any future write paths e.g. webinterface backfill).
|
||||
if (!Plugin.Config.IsAllowedForStorage(message.Code.Type))
|
||||
{
|
||||
Plugin.Log.Debug($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
||||
// Verbose-only: this fires for every dropped message, which is
|
||||
// the common case for users with a tight privacy whitelist. Keep
|
||||
// it for diagnostics but stay out of the default xllog stream.
|
||||
Plugin.Log.Verbose($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,15 +533,16 @@ internal class MessageStore : IDisposable
|
||||
DateTimeOffset? from,
|
||||
DateTimeOffset? to)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
var clauses = new List<string> { "deleted = false" };
|
||||
if (chatTypes is { Count: > 0 })
|
||||
clauses.Add($"ChatType IN ({string.Join(",", chatTypes)})");
|
||||
clauses.Add($"ChatType IN ({BindIntList(cmd, "exct", chatTypes)})");
|
||||
if (from is not null)
|
||||
clauses.Add("Date >= $From");
|
||||
if (to is not null)
|
||||
clauses.Add("Date <= $To");
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
Id,
|
||||
@@ -693,16 +715,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal long CountDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||
{
|
||||
using var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "cdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
||||
|
||||
using var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -722,16 +745,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "gdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -763,16 +787,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal MessageEnumerator GetPagedDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null, int page = 0)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "pdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -806,6 +831,24 @@ internal class MessageStore : IDisposable
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader());
|
||||
}
|
||||
|
||||
// Build "$prefix0,$prefix1,..." placeholder list and bind values to
|
||||
// the command. SQLite has no native array parameter, so we generate
|
||||
// the list at runtime and bind each entry under its own name. Used
|
||||
// for IN-clauses and similar dynamic-arity SQL fragments.
|
||||
private static string BindIntList(SqliteCommand cmd, string prefix, IEnumerable<int> values)
|
||||
{
|
||||
var names = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var value in values)
|
||||
{
|
||||
var name = $"${prefix}{index}";
|
||||
cmd.Parameters.AddWithValue(name, value);
|
||||
names.Add(name);
|
||||
index++;
|
||||
}
|
||||
return string.Join(",", names);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, IDisposable, IAsyncDisposable
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
@@ -22,13 +22,13 @@ using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Action = System.Action;
|
||||
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
||||
using ChatTwoPartyFinderPayload = ChatTwo.Util.PartyFinderPayload;
|
||||
using ChatTwoPartyFinderPayload = HellionChat.Util.PartyFinderPayload;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public sealed class PayloadHandler
|
||||
{
|
||||
private const string PopupId = "chat2-context-popup";
|
||||
private const string PopupId = "hellionchat-context-popup";
|
||||
|
||||
private ChatLogWindow LogWindow { get; }
|
||||
private (Chunk, Payload?)? Popup { get; set; }
|
||||
@@ -332,10 +332,19 @@ public sealed class PayloadHandler
|
||||
atkBase->SetPosition((short) x, (short) y);
|
||||
}
|
||||
|
||||
private const float MaxInlineIconSize = 32f;
|
||||
|
||||
private static void InlineIcon(IDalamudTextureWrap icon)
|
||||
{
|
||||
if (icon.Size.X <= 0 || icon.Size.Y <= 0)
|
||||
return;
|
||||
|
||||
var width = (float) icon.Size.X;
|
||||
var height = (float) icon.Size.Y;
|
||||
var scale = Math.Min(1f, Math.Min(MaxInlineIconSize / width, MaxInlineIconSize / height));
|
||||
var size = ImGuiHelpers.ScaledVector2(width * scale, height * scale);
|
||||
|
||||
var cursor = ImGui.GetCursorPos();
|
||||
var size = ImGuiHelpers.ScaledVector2(32, 32);
|
||||
ImGui.Image(icon.Handle, size);
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPos(cursor + new Vector2(size.X + 4, size.Y - ImGui.GetTextLineHeightWithSpacing()));
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using ChatTwo.Ipc;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using System.IO;
|
||||
using HellionChat.Ipc;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.IoC;
|
||||
@@ -13,7 +14,7 @@ using Dalamud.Plugin.Services;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.ImGuiFileDialog;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
public sealed class Plugin : IDalamudPlugin
|
||||
@@ -62,6 +63,8 @@ public sealed class Plugin : IDalamudPlugin
|
||||
internal ExtraChat ExtraChat { get; }
|
||||
internal TypingIpc TypingIpc { get; }
|
||||
internal FontManager FontManager { get; }
|
||||
internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!;
|
||||
internal Ui.StatusBar StatusBar { get; private set; } = null!;
|
||||
|
||||
internal int DeferredSaveFrames = -1;
|
||||
|
||||
@@ -71,8 +74,11 @@ public sealed class Plugin : IDalamudPlugin
|
||||
// plugin start would launch two sweeps in parallel and the second one
|
||||
// would just re-do work the first one already finished. The lock guards
|
||||
// the flag — the flag check itself bails before we touch the database.
|
||||
// Volatile because the ImGui thread reads the flag outside the lock to
|
||||
// gate the manual button; without it the JIT may cache the value in a
|
||||
// register and miss the background-thread update.
|
||||
internal readonly object RetentionSweepLock = new();
|
||||
internal bool RetentionSweepRunning;
|
||||
internal volatile bool RetentionSweepRunning;
|
||||
|
||||
internal DateTime GameStarted { get; }
|
||||
|
||||
@@ -90,6 +96,12 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
public Plugin()
|
||||
{
|
||||
// Refuse to start if upstream Chat 2 is loaded — prevents IPC
|
||||
// channel collisions and double-replacement of the in-game chat
|
||||
// window. Throwing here makes Dalamud abort the load cleanly with
|
||||
// our localized message instead of crashing FFXIV mid-frame.
|
||||
ChatTwoConflictDetector.ThrowIfChatTwoIsLoaded(Interface);
|
||||
|
||||
try
|
||||
{
|
||||
GameStarted = Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
||||
@@ -107,94 +119,304 @@ public sealed class Plugin : IDalamudPlugin
|
||||
// Drop them on load to guarantee the session-only invariant.
|
||||
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// TODO Remove after 01.07.2026
|
||||
// Migrate old channel values
|
||||
if (Config.Version <= 5)
|
||||
// Hellion Chat v9 → v10 — wipes the configuration so the new 8-tab
|
||||
// layout starts from defaults instead of mapping every previous setting
|
||||
// to its new position. Backup-Failure ist non-fatal, der Wipe läuft
|
||||
// trotzdem; dem User fehlt dann nur das manuelle Restore-Sicherheitsnetz.
|
||||
if (Config.Version < 10)
|
||||
{
|
||||
foreach (var tab in Config.Tabs)
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is not null)
|
||||
{
|
||||
if (tab.ChatCodes.Count > 0)
|
||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v10-backup");
|
||||
|
||||
try
|
||||
{
|
||||
tab.SelectedChannels = tab.ChatCodes.ToDictionary(pair => pair.Key, pair => (pair.Value, pair.Value));
|
||||
tab.ChatCodes.Clear();
|
||||
if (File.Exists(liveConfigPath))
|
||||
{
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v10 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.InactivityHideChannels.Count > 0)
|
||||
Config = new Configuration
|
||||
{
|
||||
Config.InactivityHideChannelsV2 = Config.InactivityHideChannels.ToDictionary(pair => pair.Key, pair => (pair.Value, pair.Value));
|
||||
Config.InactivityHideChannels.Clear();
|
||||
}
|
||||
|
||||
Config.Version = 6;
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
// Hellion Chat v6→v7: seed Privacy-First defaults.
|
||||
if (Config.Version <= 6)
|
||||
{
|
||||
Config.PrivacyFilterEnabled = true;
|
||||
Config.PrivacyPersistChannels = [..Privacy.PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
Config.PrivacyPersistUnknownChannels = false;
|
||||
// Existing ChatTwo users skip the first-run wizard — the
|
||||
// migration toast already explains what changed and they
|
||||
// can reopen the wizard from Settings → Privacy if they
|
||||
// want to pick a different profile.
|
||||
Config.FirstRunCompleted = true;
|
||||
Config.Version = 7;
|
||||
Version = 10,
|
||||
FirstRunCompleted = true,
|
||||
};
|
||||
SaveConfig();
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.Migration_Notification_Title,
|
||||
Content = HellionStrings.Migration_Notification_Content,
|
||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(15),
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion Chat v7→v8: webinterface removed in 0.2.0. Old config
|
||||
// entries (WebinterfacePassword, AuthStore, etc.) get dropped on
|
||||
// the next save because their properties no longer exist on the
|
||||
// Configuration class. The bump is recorded so the notification
|
||||
// only fires once.
|
||||
if (Config.Version <= 7)
|
||||
// Hellion Chat v10 → v11 — adds the global Configuration.PopOutInputEnabled
|
||||
// master switch and SeenPopOutInputHint flag for the v0.6.0 pop-out
|
||||
// input feature. Lightweight migration: defaults both fields,
|
||||
// no user-facing notification because the change is opt-in only.
|
||||
if (Config.Version < 11)
|
||||
{
|
||||
Config.Version = 8;
|
||||
Config.PopOutInputEnabled = false;
|
||||
Config.SeenPopOutInputHint = false;
|
||||
Config.Version = 11;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v10 → v11: PopOutInputEnabled added (global, default off), " +
|
||||
"SeenPopOutInputHint added (default false)");
|
||||
}
|
||||
|
||||
// Hellion Chat v11 → v12 — flips Configuration.PopOutInputEnabled from
|
||||
// the v0.6.0 opt-in default (false) to opt-out (true) per v0.6.1 UX
|
||||
// polish. Hard-flip is a deliberate design call (see Spec section 5.7);
|
||||
// users are notified via the v0.6.1 hint banner (SeenPopOutHeaderHint
|
||||
// reset). Re-toggle after migration is preserved because this block
|
||||
// only fires for Version < 12.
|
||||
if (Config.Version < 12)
|
||||
{
|
||||
Config.PopOutInputEnabled = true;
|
||||
Config.SeenPopOutHeaderHint = false;
|
||||
Config.Version = 12;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v11 → v12: PopOutInputEnabled hard-flipped to true (v0.6.1 default), " +
|
||||
"SeenPopOutHeaderHint reset to false (v0.6.1 banner re-armed)");
|
||||
}
|
||||
|
||||
// Hellion Chat v12 → v13 — hard-resets the tab layout to the
|
||||
// sharpened v1.0.0 defaults (5 thematic tabs, see TabsUtil and
|
||||
// the default-fill block below). Existing tab state is wiped
|
||||
// because per-channel mapping from the old General preset to
|
||||
// the new General/System split would be ambiguous and would
|
||||
// produce subtly wrong results for users who tweaked the old
|
||||
// layout. A timestamped backup of the live config is written
|
||||
// alongside it as a manual restore safety net. The wipe scope
|
||||
// is intentionally narrow: only Config.Tabs is reset; Privacy,
|
||||
// Retention, Theme and every other knob keeps its current value.
|
||||
if (Config.Version < 13)
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is not null)
|
||||
{
|
||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v13-backup");
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v13 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
Config.Tabs.Clear();
|
||||
Config.Version = 13;
|
||||
SaveConfig();
|
||||
|
||||
Log.Information(
|
||||
"Migrated config v12 → v13: tab layout hard-reset to v1.0.0 defaults; " +
|
||||
"pre-v13 config backup written next to the live file. " +
|
||||
"Default tabs will be populated by the Tabs.Count == 0 block.");
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.Migration_Webinterface_Removed_Title,
|
||||
Content = HellionStrings.Migration_Webinterface_Removed_Content,
|
||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(20),
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion Chat v8→v9: Auto-Tell-Tabs feature seeded with
|
||||
// property-initializer defaults (enabled, limit 15, history 20,
|
||||
// section header on). No data migration needed — just bump the
|
||||
// version and notify the user once so the feature does not
|
||||
// surprise them.
|
||||
if (Config.Version <= 8)
|
||||
// Hellion Chat v13 → v14 — theme-engine migration. Alle User landen
|
||||
// auf "hellion-arctic" als neues Default-Theme; die alte
|
||||
// HellionThemeEnabled-Flag wird deprecated und nur noch ein Release
|
||||
// als Safety-Net im JSON behalten. Window-Opacity wandert von
|
||||
// HellionThemeWindowOpacity in das neue WindowOpacity-Feld.
|
||||
if (Config.Version < 14)
|
||||
{
|
||||
Config.Version = 9;
|
||||
Config.Theme = "hellion-arctic";
|
||||
// v1.2.0: alter Opacity-Wert wird nicht mehr migriert (Field entfernt).
|
||||
// User die direkt v13 → v15 springen bekommen den Default 0.85.
|
||||
Config.ReduceMotion = false;
|
||||
Config.UseCompactDensity = false;
|
||||
Config.Version = 14;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v13 → v14: theme engine introduced, all users land on hellion-arctic; " +
|
||||
"pick chat2-classic in Settings → Themes for the upstream look");
|
||||
}
|
||||
|
||||
if (Config.Version < 15)
|
||||
{
|
||||
// v1.2.0 — keine Datenmigration nötig. Removal der deprecated
|
||||
// Theme-Felder ist reine Schema-Bereinigung (System.Text.Json
|
||||
// ignoriert unbekannte Felder im JSON, daher kein Crash bei
|
||||
// Configs die noch HellionThemeEnabled/HellionThemeWindowOpacity
|
||||
// serialisiert haben — die Werte verfallen einfach).
|
||||
Config.Version = 15;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v14 → v15: legacy theme fields removed " +
|
||||
"(HellionThemeEnabled, HellionThemeWindowOpacity)");
|
||||
}
|
||||
|
||||
// Hellion Chat v15 → v16 — Settings Cleanup. Re-Sortierung der
|
||||
// Tabs auf der UI-Seite (datenneutral). 4 tote Felder verfallen
|
||||
// beim System.Text.Json-Deserialize (OverrideStyle, ChosenStyle,
|
||||
// WindowAlpha, ShowThemeQuickPicker — sind alle nicht mehr im
|
||||
// Configuration-Schema definiert). WindowAlpha wird zuvor auf
|
||||
// WindowOpacity gemappt damit User die ihn gesetzt hatten ihre
|
||||
// Transparenz-Einstellung behalten.
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
var liveConfigPath = pluginConfigsDir is not null
|
||||
? Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json")
|
||||
: null;
|
||||
|
||||
// Backup-Datei neben der live Config — Pattern aus v13 Branch.
|
||||
if (pluginConfigsDir is not null && liveConfigPath is not null)
|
||||
{
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v16-backup");
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v16 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-v16 Felder einmalig roh aus dem JSON lesen, da sie nicht
|
||||
// mehr im Configuration-Schema sind (und damit aus Config nicht
|
||||
// mehr abrufbar). WindowAlpha → WindowOpacity Mapping nur wenn
|
||||
// User WindowOpacity noch nicht selbst angefasst hat (Default
|
||||
// 0.85), sonst gewinnt der User-Wert.
|
||||
float oldWindowAlpha = 100f;
|
||||
bool oldOverrideStyle = false;
|
||||
if (liveConfigPath is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(liveConfigPath))
|
||||
{
|
||||
var rawJson = File.ReadAllText(liveConfigPath);
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(rawJson);
|
||||
if (doc.RootElement.TryGetProperty("WindowAlpha", out var alphaProp)
|
||||
&& alphaProp.ValueKind == System.Text.Json.JsonValueKind.Number)
|
||||
{
|
||||
oldWindowAlpha = alphaProp.GetSingle();
|
||||
}
|
||||
if (doc.RootElement.TryGetProperty("OverrideStyle", out var ovProp)
|
||||
&& ovProp.ValueKind is System.Text.Json.JsonValueKind.True)
|
||||
{
|
||||
oldOverrideStyle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v16 legacy-field lookup failed, defaults assumed");
|
||||
}
|
||||
}
|
||||
|
||||
if (oldWindowAlpha != 100f
|
||||
&& Math.Abs(Config.WindowOpacity - 0.85f) < 0.001f)
|
||||
{
|
||||
Config.WindowOpacity = Math.Clamp(oldWindowAlpha / 100f, 0.5f, 1.0f);
|
||||
Log.Information(
|
||||
$"Migrated WindowAlpha {oldWindowAlpha} to WindowOpacity {Config.WindowOpacity}");
|
||||
}
|
||||
else if (oldWindowAlpha != 100f)
|
||||
{
|
||||
Log.Information(
|
||||
$"Skipped WindowAlpha→WindowOpacity migration: WindowOpacity already user-set " +
|
||||
$"({Config.WindowOpacity}), legacy WindowAlpha value {oldWindowAlpha} dropped.");
|
||||
}
|
||||
|
||||
if (oldOverrideStyle)
|
||||
{
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.AutoTellTabs_Migration_Title,
|
||||
Content = HellionStrings.AutoTellTabs_Migration_Content,
|
||||
Title = "Hellion Chat 1.2.1",
|
||||
Content = HellionStrings.Migration_v16_OverrideStyle_Toast,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(20),
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// v1.2.1 Default-Bumps für UX-Verbesserungen. Pattern: nur
|
||||
// migrieren wenn der User noch auf dem alten Default ist.
|
||||
// Bei bool-Werten ist die Erkennung pragmatisch — wer den
|
||||
// alten Default aktiv ausgeschaltet hatte, erlebt das als
|
||||
// Regression und stellt es einmal in den Settings zurück.
|
||||
// Der Trade-Off ist akzeptabel weil die alten Defaults in
|
||||
// v1.2.0 erst neu eingeführt wurden und kaum jemand aktiv
|
||||
// umgeschaltet hat.
|
||||
if (!Config.UseCompactDensity)
|
||||
{
|
||||
Config.UseCompactDensity = true;
|
||||
Log.Information("v16 default-bump: UseCompactDensity false → true");
|
||||
}
|
||||
if (!Config.HideInNewGamePlusMenu)
|
||||
{
|
||||
Config.HideInNewGamePlusMenu = true;
|
||||
Log.Information("v16 default-bump: HideInNewGamePlusMenu false → true");
|
||||
}
|
||||
if (!Config.HideSameTimestamps)
|
||||
{
|
||||
Config.HideSameTimestamps = true;
|
||||
Log.Information("v16 default-bump: HideSameTimestamps false → true");
|
||||
}
|
||||
if (Config.MaxLinesToRender == 5000)
|
||||
{
|
||||
Config.MaxLinesToRender = 2500;
|
||||
Log.Information("v16 default-bump: MaxLinesToRender 5000 → 2500");
|
||||
}
|
||||
if (Config.ChatColours.Count == 0)
|
||||
{
|
||||
foreach (var (channel, colour) in Resources.ChatColourPresets.All["Hellion"].Colours)
|
||||
Config.ChatColours[channel] = colour;
|
||||
Log.Information("v16 default-bump: ChatColours empty → Hellion brand preset");
|
||||
}
|
||||
|
||||
Config.Version = 16;
|
||||
SaveConfig();
|
||||
Log.Information(
|
||||
"Migrated config v15 → v16: settings cleanup, " +
|
||||
"OverrideStyle/ChosenStyle/WindowAlpha/ShowThemeQuickPicker dropped from schema");
|
||||
}
|
||||
|
||||
// Hellion v1.0.0 default tab layout. Five thematically separated
|
||||
// tabs: General catches the immediate-surroundings public chat
|
||||
// (Say/Yell/Shout) only; System absorbs the rest of the technical
|
||||
// and gameplay-event noise; FreeCompany, Group and Linkshell each
|
||||
// own their respective channel set. Tells are not in a static
|
||||
// tab anymore — Auto-Tell-Tabs spawns dedicated per-conversation
|
||||
// tabs on demand. Novice-Network gets no preset tab; users who
|
||||
// want it can add HellionBeginner from Settings → Tabs.
|
||||
if (Config.Tabs.Count == 0)
|
||||
{
|
||||
Config.Tabs.Add(TabsUtil.VanillaGeneral);
|
||||
Config.Tabs.Add(TabsUtil.HellionSystem);
|
||||
Config.Tabs.Add(TabsUtil.HellionFreeCompany);
|
||||
Config.Tabs.Add(TabsUtil.HellionParty);
|
||||
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
||||
}
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
ImGuiUtil.Initialize(this);
|
||||
@@ -208,6 +430,16 @@ public sealed class Plugin : IDalamudPlugin
|
||||
ExtraChat = new ExtraChat();
|
||||
FontManager = new FontManager();
|
||||
|
||||
// v1.1.0 — Theme-Engine init. Custom-Themes liegen in
|
||||
// pluginConfigs/HellionChat/themes/, lazy geladen beim ersten Get.
|
||||
var customThemesDir = Path.Combine(Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(customThemesDir);
|
||||
SeedExampleThemeIfEmpty(customThemesDir);
|
||||
ThemeRegistry = new Themes.ThemeRegistry(customThemesDir);
|
||||
ThemeRegistry.Switch(Config.Theme);
|
||||
|
||||
StatusBar = new Ui.StatusBar();
|
||||
|
||||
MessageManager = new MessageManager(this); // Does it require UI?
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs service. Subscribes to the
|
||||
@@ -267,7 +499,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
||||
|
||||
if (Config.ShowEmotes)
|
||||
Task.Run(EmoteCache.LoadData);
|
||||
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||
|
||||
#if !DEBUG
|
||||
// Avoid 300ms hitch when sending first message by preloading the
|
||||
@@ -471,10 +703,15 @@ public sealed class Plugin : IDalamudPlugin
|
||||
if (deleted > 0)
|
||||
{
|
||||
Log.Information($"Retention sweep deleted {deleted} expired messages.");
|
||||
// Run the clear+refilter synchronously on the framework thread.
|
||||
// Earlier this called FilterAllTabsAsync(), which is fire-and-forget
|
||||
// — the .Wait() here would return as soon as the inner Task.Run was
|
||||
// dispatched, racing the next sweep cycle against the still-running
|
||||
// filter pass. See AUDIT-2026-05-05 [QUAL-02].
|
||||
Framework.Run(() =>
|
||||
{
|
||||
MessageManager.ClearAllTabs();
|
||||
MessageManager.FilterAllTabsAsync();
|
||||
MessageManager.FilterAllTabs();
|
||||
}).Wait();
|
||||
}
|
||||
else
|
||||
@@ -496,13 +733,10 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
// Hellion theme is pushed once per frame here so every plugin window
|
||||
// (chat log, settings, viewers, wizard, file dialog) renders with
|
||||
// the same palette. Skipping the push leaves the upstream Dalamud
|
||||
// look untouched for users who flipped the toggle off.
|
||||
using IDisposable? _style = Config.HellionThemeEnabled
|
||||
? HellionStyle.PushGlobal(Config.HellionThemeWindowOpacity)
|
||||
: null;
|
||||
// Theme-Engine ist ab v14 immer aktiv; Klassik ist jetzt ein eigenes
|
||||
// Theme statt einem deaktivierten Hellion-Theme. Active wird einmal
|
||||
// pro Frame aus der Registry gelesen.
|
||||
using IDisposable _style = HellionStyle.PushGlobal(ThemeRegistry.Active, Config.WindowOpacity);
|
||||
|
||||
ChatLogWindow.BeginFrame();
|
||||
|
||||
@@ -513,6 +747,16 @@ public sealed class Plugin : IDalamudPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
// v1.0.2 — global skip while the New Game+ menu (QuestRedo addon) is
|
||||
// open. Hides every plugin window in one shot (chat log, pop-outs,
|
||||
// settings, db viewer, etc.), matching the LoadingScreens pattern.
|
||||
if (Config.HideInNewGamePlusMenu && GameFunctions.GameFunctions.IsAddonInteractable(GameFunctions.GameFunctions.NewGamePlusAddonName))
|
||||
{
|
||||
ChatLogWindow.FinalizeFrame();
|
||||
TypingIpc.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
ChatLogWindow.HideStateCheck();
|
||||
|
||||
Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden;
|
||||
@@ -577,4 +821,36 @@ public sealed class Plugin : IDalamudPlugin
|
||||
public static bool InBattle => Condition[ConditionFlag.InCombat];
|
||||
public static bool GposeActive => Condition[ConditionFlag.WatchingCutscene];
|
||||
public static bool CutsceneActive => Condition[ConditionFlag.OccupiedInCutSceneEvent] || Condition[ConditionFlag.WatchingCutscene78];
|
||||
|
||||
// v1.1.0 — wenn der themes/-Ordner leer ist, schreiben wir die embedded
|
||||
// example-theme.json als Vorlage rein. Bestehende User-Customs werden
|
||||
// nicht angefasst (existing JSONs lassen den Block überspringen).
|
||||
private static void SeedExampleThemeIfEmpty(string dir)
|
||||
{
|
||||
if (Directory.EnumerateFiles(dir, "*.json").Any())
|
||||
return;
|
||||
|
||||
var examplePath = Path.Combine(dir, "example-theme.json");
|
||||
var resourceStream = typeof(Plugin).Assembly.GetManifestResourceStream("HellionChat.Themes.Builtin.example-theme.json");
|
||||
if (resourceStream is null)
|
||||
{
|
||||
Log.Warning("Themes example template not found in assembly resources; skipping seed.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var fileStream = File.Create(examplePath);
|
||||
resourceStream.CopyTo(fileStream);
|
||||
Log.Information($"Seeded example-theme.json into {dir}");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed to seed example-theme.json; user can create custom themes manually.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourceStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
|
||||
namespace ChatTwo.Privacy;
|
||||
namespace HellionChat.Privacy;
|
||||
|
||||
internal static class PrivacyDefaults
|
||||
{
|
||||
@@ -0,0 +1,316 @@
|
||||
using System.Collections.Generic;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Resources;
|
||||
|
||||
// Hellion Chat — v0.6.0 built-in colour presets for the ChatColours
|
||||
// settings section. Read-only static data; users apply a preset via the
|
||||
// settings UI which overwrites Configuration.ChatColours immediately.
|
||||
// Battle-channel types are intentionally NOT covered by the stylistic
|
||||
// presets so that combat-log tuning the user has done stays intact.
|
||||
public sealed record ChatColourPreset(
|
||||
string DisplayName,
|
||||
string LocalizationKey,
|
||||
bool IsBrandPreset,
|
||||
IReadOnlyDictionary<ChatType, uint> Colours);
|
||||
|
||||
public static class ChatColourPresets
|
||||
{
|
||||
public static IReadOnlyDictionary<string, ChatColourPreset> All { get; } = BuildAll();
|
||||
|
||||
private static Dictionary<string, ChatColourPreset> BuildAll()
|
||||
{
|
||||
return new Dictionary<string, ChatColourPreset>
|
||||
{
|
||||
["Default"] = new(
|
||||
DisplayName: "ChatTwo Default",
|
||||
LocalizationKey: "ChatColourPresets_Default",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildDefault()),
|
||||
["HighContrast"] = new(
|
||||
DisplayName: "High-Contrast",
|
||||
LocalizationKey: "ChatColourPresets_HighContrast",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildHighContrast()),
|
||||
["Pastell"] = new(
|
||||
DisplayName: "Pastell",
|
||||
LocalizationKey: "ChatColourPresets_Pastell",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildPastell()),
|
||||
["DarkModeTuned"] = new(
|
||||
DisplayName: "Dark-Mode-Tuned",
|
||||
LocalizationKey: "ChatColourPresets_DarkModeTuned",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildDarkModeTuned()),
|
||||
["Hellion"] = new(
|
||||
DisplayName: "Hellion",
|
||||
LocalizationKey: "ChatColourPresets_Hellion",
|
||||
IsBrandPreset: true,
|
||||
Colours: BuildHellion()),
|
||||
["NightBlue"] = new(
|
||||
DisplayName: "Night Blue",
|
||||
LocalizationKey: "ChatColourPresets_NightBlue",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildNightBlue()),
|
||||
["IndigoViolet"] = new(
|
||||
DisplayName: "Indigo Violet",
|
||||
LocalizationKey: "ChatColourPresets_IndigoViolet",
|
||||
IsBrandPreset: false,
|
||||
Colours: BuildIndigoViolet()),
|
||||
};
|
||||
}
|
||||
|
||||
// The Default preset spiegelt 1:1 die Werte aus ChatTypeExt.DefaultColor.
|
||||
// Channels ohne Default-Wert (return null) werden ausgelassen — wer sie
|
||||
// anwenden will, behält seine aktuelle Farbe.
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildDefault()
|
||||
{
|
||||
var dict = new Dictionary<ChatType, uint>();
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
var def = type.DefaultColor();
|
||||
if (def.HasValue)
|
||||
dict[type] = def.Value;
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildHighContrast()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(255, 255, 255),
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 192, 0),
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 96, 0),
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 128, 255),
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 128, 255),
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(128, 192, 255),
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 128, 64),
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(96, 192, 255),
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 64),
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128),
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 128),
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128),
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 128),
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(128, 255, 192),
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(128, 192, 255),
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 128, 255),
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 128, 192),
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 96, 96),
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 96),
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 96),
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 96),
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(96, 255, 160),
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(96, 160, 255),
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 96, 255),
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 96, 160),
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildPastell()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(232, 232, 232),
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(245, 216, 155),
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(245, 176, 155),
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(224, 176, 224),
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(224, 176, 224),
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(176, 204, 224),
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(224, 192, 160),
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(168, 200, 224),
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(200, 224, 176),
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(224, 176, 176),
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(224, 200, 176),
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(224, 224, 176),
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(200, 224, 176),
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(176, 224, 200),
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(176, 200, 224),
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(200, 176, 224),
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(224, 176, 200),
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(224, 160, 160),
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(224, 192, 160),
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(224, 224, 160),
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(192, 224, 160),
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(160, 224, 192),
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(160, 192, 224),
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(192, 160, 224),
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(224, 160, 192),
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildDarkModeTuned()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 240, 240),
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 208, 64),
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 128, 64),
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 160, 255),
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 160, 255),
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(160, 208, 255),
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 160, 96),
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(128, 200, 255),
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 96),
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 160, 160),
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 160),
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 160),
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 160),
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(160, 255, 192),
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(160, 192, 255),
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 160, 255),
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 160, 192),
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128),
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 128),
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128),
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 128),
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(128, 255, 160),
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(128, 160, 255),
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 128, 255),
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 128, 160),
|
||||
};
|
||||
}
|
||||
|
||||
// Hellion brand preset — Arctic Cyan + Ember Orange palette aus
|
||||
// /mnt/ssd-fast/Projekte/hellion-media/hellion-media-website/BRANDING.md
|
||||
// (Schema-Stand 2026-04-16). Channels sind über das ganze Brand-Spektrum
|
||||
// verteilt damit jede Zeile auf einen Glance unterscheidbar ist:
|
||||
// Cyan-Familie für Standard/Tell, Ember + Warning für laute Channels,
|
||||
// Status-Farben (Success, Danger) für Linkshells. CrossLinkshells
|
||||
// nutzen die dunkleren/sattersten Varianten derselben Hue-Familien.
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildHellion()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
// Standard / Tell — Cyan-Familie (Brand-Primary)
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light #4DD9E8
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan #00BED2
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark #0097A7
|
||||
|
||||
// Laute Channels — Ember/Warning
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning #F0AD4E
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember #F97316
|
||||
|
||||
// Gruppen-Channels — Success/Ember-dark/Cyan
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success #5CB85C
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark #E85D04
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(77, 217, 232),// Cyan-light
|
||||
|
||||
// Linkshells 1-8 — über das ganze Brand-Spektrum verteilt
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(251, 146, 60), // Ember-light #FB923C
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(217, 83, 79), // Danger #D9534F
|
||||
|
||||
// CrossWorld-Linkshells 1-8 — dunklere/sattersere Varianten
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(200, 140, 50), // Warning-dark
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(60, 140, 60), // Success-dark
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(0, 110, 130), // Cyan-darker
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(220, 90, 30), // Ember-medium
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(170, 60, 60), // Danger-dark
|
||||
};
|
||||
}
|
||||
|
||||
// Bonus preset — Night Blue, KAZAMA-Stimmungs-Theme aus
|
||||
// /mnt/HDD-Data1/Obsidian/Vault/Systeme/KAZAMA/Theming/Night Blue + Indigo Violet Themes.md
|
||||
// Klassisch, kühl, technisch — Marineblau-Tiefe ohne Lila-Anteil.
|
||||
// Bewusst NICHT als Brand-Preset markiert (Vault-Boundary): die KAZAMA-Themes
|
||||
// sind persönliche Stimmungs-Themes, nicht Teil des Hellion-Brand-Systems.
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildNightBlue()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
// Standard / Tell — Royal Blue Akzent-Familie
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(230, 237, 247), // text-primary
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(106, 176, 255),// akzent-hot
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
|
||||
|
||||
// Laute Channels — Warning/Danger Status-Töne
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74), // warning
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122), // danger
|
||||
|
||||
// Gruppen — Success/Akzent-Variations
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151), // success
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100), // warm-orange-light
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(140, 160, 191),// text-dim
|
||||
|
||||
// Linkshells 1-8 — über Spektrum verteilt
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 220, 130),
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(130, 220, 100),
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(61, 220, 151),
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(100, 200, 220),
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(106, 176, 255),
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(140, 160, 191),
|
||||
|
||||
// CrossWorld-Linkshells — gedämpfte Variants
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(200, 130, 50),
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(220, 110, 80),
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(200, 180, 60),
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(90, 180, 80),
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(30, 170, 110),
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(50, 130, 170),
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(50, 110, 180),
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(90, 100, 130),
|
||||
};
|
||||
}
|
||||
|
||||
// Bonus preset — Indigo Violet, KAZAMA-Stimmungs-Theme aus demselben
|
||||
// Vault-Doc. Warm-mystisch, "Galaxy/Glitter/Nordlicht" — tiefes Indigo
|
||||
// mit kräftigem Violet-Akzent. Persönlicher Favorit (siehe Vault).
|
||||
// Auch nicht als Brand-Preset (siehe NightBlue-Note oben).
|
||||
private static IReadOnlyDictionary<ChatType, uint> BuildIndigoViolet()
|
||||
{
|
||||
return new Dictionary<ChatType, uint>
|
||||
{
|
||||
// Standard / Tell — Royal Violet Akzent-Familie
|
||||
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 230, 255), // text-primary (light lavender)
|
||||
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(176, 124, 255),// akzent-hot
|
||||
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
|
||||
|
||||
// Laute Channels — geteilt mit Night Blue (Status-Farben)
|
||||
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122),
|
||||
|
||||
// Gruppen
|
||||
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151),
|
||||
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
|
||||
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(168, 144, 208),// text-dim
|
||||
|
||||
// Linkshells 1-8
|
||||
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 220, 130),
|
||||
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(200, 124, 255),
|
||||
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(176, 124, 255),
|
||||
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(139, 77, 222),
|
||||
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(130, 90, 200),
|
||||
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(168, 144, 208),
|
||||
|
||||
// CrossWorld-Linkshells
|
||||
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(200, 130, 50),
|
||||
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(220, 110, 80),
|
||||
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(200, 180, 60),
|
||||
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(130, 80, 180),
|
||||
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(100, 60, 160),
|
||||
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(91, 42, 154),
|
||||
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(80, 50, 130),
|
||||
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(117, 96, 160),
|
||||
};
|
||||
}
|
||||
}
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// Hand-maintained strongly-typed accessor for HellionStrings.resx.
|
||||
// Mirrors the layout of Language.Designer.cs so the same Plugin.cs
|
||||
// LanguageChanged handler can update Culture for both classes.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace HellionChat.Resources;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
|
||||
internal class HellionStrings
|
||||
{
|
||||
private static global::System.Resources.ResourceManager? resourceMan;
|
||||
private static global::System.Globalization.CultureInfo? resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal HellionStrings() { }
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (resourceMan is null)
|
||||
resourceMan = new global::System.Resources.ResourceManager("HellionChat.Resources.HellionStrings", typeof(HellionStrings).Assembly);
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo? Culture
|
||||
{
|
||||
get => resourceCulture;
|
||||
set => resourceCulture = value;
|
||||
}
|
||||
|
||||
private static string Get(string key)
|
||||
=> ResourceManager.GetString(key, resourceCulture) ?? key;
|
||||
|
||||
internal static string Privacy_Tab_Title => Get(nameof(Privacy_Tab_Title));
|
||||
internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name));
|
||||
internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description));
|
||||
internal static string Privacy_FilterEnabled_StorageOnly_Help => Get(nameof(Privacy_FilterEnabled_StorageOnly_Help));
|
||||
internal static string Privacy_Filter_Tree_Heading => Get(nameof(Privacy_Filter_Tree_Heading));
|
||||
internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help));
|
||||
internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst));
|
||||
internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll));
|
||||
internal static string Privacy_Preset_SelectAll => Get(nameof(Privacy_Preset_SelectAll));
|
||||
internal static string Privacy_Group_DirectMessages => Get(nameof(Privacy_Group_DirectMessages));
|
||||
internal static string Privacy_Group_PartyAlliance => Get(nameof(Privacy_Group_PartyAlliance));
|
||||
internal static string Privacy_Group_FreeCompany => Get(nameof(Privacy_Group_FreeCompany));
|
||||
internal static string Privacy_Group_Linkshells => Get(nameof(Privacy_Group_Linkshells));
|
||||
internal static string Privacy_Group_CrossLinkshells => Get(nameof(Privacy_Group_CrossLinkshells));
|
||||
internal static string Privacy_Group_ExtraChat => Get(nameof(Privacy_Group_ExtraChat));
|
||||
internal static string Privacy_Group_PublicChat => Get(nameof(Privacy_Group_PublicChat));
|
||||
internal static string Privacy_Group_SystemLogs => Get(nameof(Privacy_Group_SystemLogs));
|
||||
internal static string Privacy_PersistUnknown_Name => Get(nameof(Privacy_PersistUnknown_Name));
|
||||
internal static string Privacy_PersistUnknown_Description => Get(nameof(Privacy_PersistUnknown_Description));
|
||||
|
||||
internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading));
|
||||
internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro));
|
||||
internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote));
|
||||
internal static string Cleanup_Preview_Stale => Get(nameof(Cleanup_Preview_Stale));
|
||||
internal static string Retention_Help_SavedNote => Get(nameof(Retention_Help_SavedNote));
|
||||
internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview));
|
||||
internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview));
|
||||
internal static string Cleanup_TotalStored => Get(nameof(Cleanup_TotalStored));
|
||||
internal static string Cleanup_WillKeep => Get(nameof(Cleanup_WillKeep));
|
||||
internal static string Cleanup_WillDelete => Get(nameof(Cleanup_WillDelete));
|
||||
internal static string Cleanup_Breakdown => Get(nameof(Cleanup_Breakdown));
|
||||
internal static string Cleanup_Marker_Keep => Get(nameof(Cleanup_Marker_Keep));
|
||||
internal static string Cleanup_Marker_Delete => Get(nameof(Cleanup_Marker_Delete));
|
||||
internal static string Cleanup_Apply_Label => Get(nameof(Cleanup_Apply_Label));
|
||||
internal static string Cleanup_Apply_Tooltip => Get(nameof(Cleanup_Apply_Tooltip));
|
||||
internal static string Cleanup_Running => Get(nameof(Cleanup_Running));
|
||||
internal static string Cleanup_PreviewError => Get(nameof(Cleanup_PreviewError));
|
||||
internal static string Cleanup_Success => Get(nameof(Cleanup_Success));
|
||||
internal static string Cleanup_Error => Get(nameof(Cleanup_Error));
|
||||
|
||||
internal static string Retention_Heading => Get(nameof(Retention_Heading));
|
||||
internal static string Retention_Enabled_Name => Get(nameof(Retention_Enabled_Name));
|
||||
internal static string Retention_Enabled_Description => Get(nameof(Retention_Enabled_Description));
|
||||
internal static string Retention_Default_Label => Get(nameof(Retention_Default_Label));
|
||||
internal static string Retention_Default_Help => Get(nameof(Retention_Default_Help));
|
||||
internal static string Retention_Reset_Spec => Get(nameof(Retention_Reset_Spec));
|
||||
internal static string Retention_Clear_Overrides => Get(nameof(Retention_Clear_Overrides));
|
||||
internal static string Retention_Tree_Heading => Get(nameof(Retention_Tree_Heading));
|
||||
internal static string Retention_Tag_Override => Get(nameof(Retention_Tag_Override));
|
||||
internal static string Retention_Tag_Spec => Get(nameof(Retention_Tag_Spec));
|
||||
internal static string Retention_Tag_Global => Get(nameof(Retention_Tag_Global));
|
||||
internal static string Retention_Reset_Button => Get(nameof(Retention_Reset_Button));
|
||||
internal static string Retention_Apply_Label => Get(nameof(Retention_Apply_Label));
|
||||
internal static string Retention_Apply_Tooltip => Get(nameof(Retention_Apply_Tooltip));
|
||||
internal static string Retention_Running => Get(nameof(Retention_Running));
|
||||
internal static string Retention_LastRun_Never => Get(nameof(Retention_LastRun_Never));
|
||||
internal static string Retention_LastRun_At => Get(nameof(Retention_LastRun_At));
|
||||
internal static string Retention_Success => Get(nameof(Retention_Success));
|
||||
internal static string Retention_Error => Get(nameof(Retention_Error));
|
||||
|
||||
internal static string Wizard_Title => Get(nameof(Wizard_Title));
|
||||
internal static string Wizard_Intro => Get(nameof(Wizard_Intro));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Heading => Get(nameof(Wizard_Profile_PrivacyFirst_Heading));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Description => Get(nameof(Wizard_Profile_PrivacyFirst_Description));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Apply => Get(nameof(Wizard_Profile_PrivacyFirst_Apply));
|
||||
internal static string Wizard_Profile_Casual_Heading => Get(nameof(Wizard_Profile_Casual_Heading));
|
||||
internal static string Wizard_Profile_Casual_Description => Get(nameof(Wizard_Profile_Casual_Description));
|
||||
internal static string Wizard_Profile_Casual_Apply => Get(nameof(Wizard_Profile_Casual_Apply));
|
||||
internal static string Wizard_Profile_FullHistory_Heading => Get(nameof(Wizard_Profile_FullHistory_Heading));
|
||||
internal static string Wizard_Profile_FullHistory_Description => Get(nameof(Wizard_Profile_FullHistory_Description));
|
||||
internal static string Wizard_Profile_FullHistory_GdprWarning => Get(nameof(Wizard_Profile_FullHistory_GdprWarning));
|
||||
internal static string Wizard_Profile_FullHistory_Apply => Get(nameof(Wizard_Profile_FullHistory_Apply));
|
||||
internal static string Wizard_Reopen_Button => Get(nameof(Wizard_Reopen_Button));
|
||||
|
||||
internal static string Export_Heading => Get(nameof(Export_Heading));
|
||||
internal static string Export_Help => Get(nameof(Export_Help));
|
||||
internal static string Export_Range_Label => Get(nameof(Export_Range_Label));
|
||||
internal static string Export_Sender_Label => Get(nameof(Export_Sender_Label));
|
||||
internal static string Export_Channels_Heading => Get(nameof(Export_Channels_Heading));
|
||||
internal static string Export_Channels_AllOff => Get(nameof(Export_Channels_AllOff));
|
||||
internal static string Export_Format_Label => Get(nameof(Export_Format_Label));
|
||||
internal static string Export_Format_Markdown => Get(nameof(Export_Format_Markdown));
|
||||
internal static string Export_Format_Json => Get(nameof(Export_Format_Json));
|
||||
internal static string Export_Format_Csv => Get(nameof(Export_Format_Csv));
|
||||
internal static string Export_Button => Get(nameof(Export_Button));
|
||||
internal static string Export_Dialog_Title => Get(nameof(Export_Dialog_Title));
|
||||
internal static string Export_Running => Get(nameof(Export_Running));
|
||||
internal static string Export_Success => Get(nameof(Export_Success));
|
||||
internal static string Export_Empty => Get(nameof(Export_Empty));
|
||||
internal static string Export_Error => Get(nameof(Export_Error));
|
||||
|
||||
internal static string Theme_Enabled_Name => Get(nameof(Theme_Enabled_Name));
|
||||
internal static string Theme_Enabled_Description => Get(nameof(Theme_Enabled_Description));
|
||||
internal static string Theme_WindowOpacity_Label => Get(nameof(Theme_WindowOpacity_Label));
|
||||
internal static string Theme_WindowOpacity_Help => Get(nameof(Theme_WindowOpacity_Help));
|
||||
internal static string Theme_UseHellionFont_Name => Get(nameof(Theme_UseHellionFont_Name));
|
||||
internal static string Theme_UseHellionFont_Description => Get(nameof(Theme_UseHellionFont_Description));
|
||||
|
||||
internal static string About_Maintainer_Heading => Get(nameof(About_Maintainer_Heading));
|
||||
internal static string About_Maintainer_Body => Get(nameof(About_Maintainer_Body));
|
||||
internal static string About_Maintainer_Website_Label => Get(nameof(About_Maintainer_Website_Label));
|
||||
internal static string About_Mission_Heading => Get(nameof(About_Mission_Heading));
|
||||
internal static string About_Mission_P1 => Get(nameof(About_Mission_P1));
|
||||
internal static string About_Mission_P2 => Get(nameof(About_Mission_P2));
|
||||
internal static string About_Mission_P3 => Get(nameof(About_Mission_P3));
|
||||
internal static string About_BuiltOn_Heading => Get(nameof(About_BuiltOn_Heading));
|
||||
internal static string About_BuiltOn_P1 => Get(nameof(About_BuiltOn_P1));
|
||||
internal static string About_BuiltOn_P2 => Get(nameof(About_BuiltOn_P2));
|
||||
internal static string About_BuiltOn_Upstream_Label => Get(nameof(About_BuiltOn_Upstream_Label));
|
||||
internal static string About_License_Heading => Get(nameof(About_License_Heading));
|
||||
internal static string About_License_P1 => Get(nameof(About_License_P1));
|
||||
internal static string About_License_P2 => Get(nameof(About_License_P2));
|
||||
internal static string About_License_P3 => Get(nameof(About_License_P3));
|
||||
internal static string About_SE_Heading => Get(nameof(About_SE_Heading));
|
||||
internal static string About_SE_P1 => Get(nameof(About_SE_P1));
|
||||
internal static string About_SE_P2 => Get(nameof(About_SE_P2));
|
||||
internal static string About_Localization_Heading => Get(nameof(About_Localization_Heading));
|
||||
internal static string About_Localization_P1 => Get(nameof(About_Localization_P1));
|
||||
internal static string About_Localization_P2 => Get(nameof(About_Localization_P2));
|
||||
internal static string About_Translators_TreeNode => Get(nameof(About_Translators_TreeNode));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs runtime strings
|
||||
internal static string AutoTellTabs_SectionHeader => Get(nameof(AutoTellTabs_SectionHeader));
|
||||
internal static string AutoTellTabs_HistorySeparator => Get(nameof(AutoTellTabs_HistorySeparator));
|
||||
internal static string AutoTellTabs_HistoryLoadError => Get(nameof(AutoTellTabs_HistoryLoadError));
|
||||
internal static string AutoTellTabs_GreetedTooltip => Get(nameof(AutoTellTabs_GreetedTooltip));
|
||||
internal static string AutoTellTabs_UnGreetedTooltip => Get(nameof(AutoTellTabs_UnGreetedTooltip));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
||||
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Name => Get(nameof(ChatLog_AutoTellTabs_Enable_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Description => Get(nameof(ChatLog_AutoTellTabs_Enable_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Name => Get(nameof(ChatLog_AutoTellTabs_Limit_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Description => Get(nameof(ChatLog_AutoTellTabs_Limit_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Name => Get(nameof(ChatLog_AutoTellTabs_Compact_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Description => Get(nameof(ChatLog_AutoTellTabs_Compact_Description));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Name => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Name));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Description => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Description));
|
||||
internal static string ChatLog_AutoTellTabs_OpenAsPopout_Name => Get(nameof(ChatLog_AutoTellTabs_OpenAsPopout_Name));
|
||||
internal static string ChatLog_AutoTellTabs_OpenAsPopout_Description => Get(nameof(ChatLog_AutoTellTabs_OpenAsPopout_Description));
|
||||
internal static string ChatLog_AutoTellTabs_PreloadHint => Get(nameof(ChatLog_AutoTellTabs_PreloadHint));
|
||||
internal static string ChatLog_AutoTellTabs_ConflictHint => Get(nameof(ChatLog_AutoTellTabs_ConflictHint));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Privacy settings tab
|
||||
internal static string Privacy_AutoTellTabs_Section_Title => Get(nameof(Privacy_AutoTellTabs_Section_Title));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Name => Get(nameof(Privacy_AutoTellTabs_Preload_Name));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Description => Get(nameof(Privacy_AutoTellTabs_Preload_Description));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Hint => Get(nameof(Privacy_AutoTellTabs_Preload_Hint));
|
||||
|
||||
// Hellion Chat — Settings UX Polish v10 wipe migration
|
||||
internal static string SettingsRefactor_Migration_Title => Get(nameof(SettingsRefactor_Migration_Title));
|
||||
internal static string SettingsRefactor_Migration_Content => Get(nameof(SettingsRefactor_Migration_Content));
|
||||
|
||||
// Hellion Chat — Settings UX Polish 8-tab structure
|
||||
internal static string Settings_Tab_General => Get(nameof(Settings_Tab_General));
|
||||
internal static string Settings_Tab_Appearance => Get(nameof(Settings_Tab_Appearance));
|
||||
internal static string Settings_Tab_Window => Get(nameof(Settings_Tab_Window));
|
||||
internal static string Settings_Tab_Chat => Get(nameof(Settings_Tab_Chat));
|
||||
internal static string Settings_Tab_Tabs => Get(nameof(Settings_Tab_Tabs));
|
||||
internal static string Settings_Tab_Database => Get(nameof(Settings_Tab_Database));
|
||||
internal static string Settings_Tab_Information => Get(nameof(Settings_Tab_Information));
|
||||
|
||||
// v1.1.0 — Settings card-grid overview
|
||||
internal static string Settings_Card_General_Title => Get(nameof(Settings_Card_General_Title));
|
||||
internal static string Settings_Card_General_Subtext => Get(nameof(Settings_Card_General_Subtext));
|
||||
internal static string Settings_Card_Appearance_Title => Get(nameof(Settings_Card_Appearance_Title));
|
||||
internal static string Settings_Card_Appearance_Subtext => Get(nameof(Settings_Card_Appearance_Subtext));
|
||||
internal static string Settings_Card_Themes_Title => Get(nameof(Settings_Card_Themes_Title));
|
||||
internal static string Settings_Card_Themes_Subtext => Get(nameof(Settings_Card_Themes_Subtext));
|
||||
internal static string Settings_Card_Window_Title => Get(nameof(Settings_Card_Window_Title));
|
||||
internal static string Settings_Card_Window_Subtext => Get(nameof(Settings_Card_Window_Subtext));
|
||||
internal static string Settings_Card_Chat_Title => Get(nameof(Settings_Card_Chat_Title));
|
||||
internal static string Settings_Card_Chat_Subtext => Get(nameof(Settings_Card_Chat_Subtext));
|
||||
internal static string Settings_Card_Tabs_Title => Get(nameof(Settings_Card_Tabs_Title));
|
||||
internal static string Settings_Card_Tabs_Subtext => Get(nameof(Settings_Card_Tabs_Subtext));
|
||||
internal static string Settings_Card_Privacy_Title => Get(nameof(Settings_Card_Privacy_Title));
|
||||
internal static string Settings_Card_Privacy_Subtext => Get(nameof(Settings_Card_Privacy_Subtext));
|
||||
internal static string Settings_Card_Database_Title => Get(nameof(Settings_Card_Database_Title));
|
||||
internal static string Settings_Card_Database_Subtext => Get(nameof(Settings_Card_Database_Subtext));
|
||||
internal static string Settings_Card_Information_Title => Get(nameof(Settings_Card_Information_Title));
|
||||
internal static string Settings_Card_Information_Subtext => Get(nameof(Settings_Card_Information_Subtext));
|
||||
|
||||
// v1.1.0 — Themes-Settings-Tab
|
||||
internal static string Settings_Tab_Themes => Get(nameof(Settings_Tab_Themes));
|
||||
internal static string Settings_Themes_Active => Get(nameof(Settings_Themes_Active));
|
||||
internal static string Settings_Themes_BuiltIns => Get(nameof(Settings_Themes_BuiltIns));
|
||||
internal static string Settings_Themes_Custom => Get(nameof(Settings_Themes_Custom));
|
||||
internal static string Settings_Themes_OpenFolder => Get(nameof(Settings_Themes_OpenFolder));
|
||||
internal static string Settings_Themes_ExportActive => Get(nameof(Settings_Themes_ExportActive));
|
||||
internal static string Settings_Themes_ApplyChatColors_Hint => Get(nameof(Settings_Themes_ApplyChatColors_Hint));
|
||||
internal static string Settings_Themes_ApplyChatColors_Apply => Get(nameof(Settings_Themes_ApplyChatColors_Apply));
|
||||
internal static string Settings_Themes_ApplyChatColors_Keep => Get(nameof(Settings_Themes_ApplyChatColors_Keep));
|
||||
|
||||
// Hellion Chat — General-Tab section headings
|
||||
internal static string Settings_General_Input_Heading => Get(nameof(Settings_General_Input_Heading));
|
||||
internal static string Settings_General_Audio_Heading => Get(nameof(Settings_General_Audio_Heading));
|
||||
internal static string Settings_General_Performance_Heading => Get(nameof(Settings_General_Performance_Heading));
|
||||
internal static string Settings_General_Language_Heading => Get(nameof(Settings_General_Language_Heading));
|
||||
|
||||
// Hellion Chat — Appearance-Tab section headings
|
||||
internal static string Settings_Appearance_Theme_Heading => Get(nameof(Settings_Appearance_Theme_Heading));
|
||||
internal static string Settings_Appearance_Fonts_Heading => Get(nameof(Settings_Appearance_Fonts_Heading));
|
||||
internal static string Settings_Appearance_Colours_Heading => Get(nameof(Settings_Appearance_Colours_Heading));
|
||||
internal static string Settings_Appearance_Timestamps_Heading => Get(nameof(Settings_Appearance_Timestamps_Heading));
|
||||
|
||||
// Hellion Chat — Window-Tab section headings
|
||||
internal static string Settings_Window_Hide_Heading => Get(nameof(Settings_Window_Hide_Heading));
|
||||
internal static string Settings_Window_InactivityHide_Heading => Get(nameof(Settings_Window_InactivityHide_Heading));
|
||||
internal static string Settings_Window_Frame_Heading => Get(nameof(Settings_Window_Frame_Heading));
|
||||
internal static string Settings_Window_Tooltips_Heading => Get(nameof(Settings_Window_Tooltips_Heading));
|
||||
|
||||
// Hellion Chat — Chat-Tab section headings
|
||||
internal static string Settings_Chat_AutoTellTabs_Heading => Get(nameof(Settings_Chat_AutoTellTabs_Heading));
|
||||
internal static string Settings_Chat_Behaviour_Heading => Get(nameof(Settings_Chat_Behaviour_Heading));
|
||||
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 — 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));
|
||||
internal static string Settings_Database_Stats_Heading => Get(nameof(Settings_Database_Stats_Heading));
|
||||
|
||||
// Hellion Chat — Information-Tab section headings
|
||||
internal static string Settings_Information_VersionInfo_Heading => Get(nameof(Settings_Information_VersionInfo_Heading));
|
||||
internal static string Settings_Information_About_Heading => Get(nameof(Settings_Information_About_Heading));
|
||||
internal static string Settings_Information_Changelog_Heading => Get(nameof(Settings_Information_Changelog_Heading));
|
||||
|
||||
// Hellion Chat — Default tab presets (channel-themed)
|
||||
internal static string Tabs_Presets_System => Get(nameof(Tabs_Presets_System));
|
||||
internal static string Tabs_Presets_FreeCompany => Get(nameof(Tabs_Presets_FreeCompany));
|
||||
internal static string Tabs_Presets_Party => Get(nameof(Tabs_Presets_Party));
|
||||
internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner));
|
||||
internal static string Tabs_Presets_Linkshell => Get(nameof(Tabs_Presets_Linkshell));
|
||||
internal static string Tabs_Presets_Linkshell_Hint => Get(nameof(Tabs_Presets_Linkshell_Hint));
|
||||
|
||||
// Hellion Chat — v1.2.0 per-tab icon override
|
||||
internal static string Tabs_Icon_Label => Get(nameof(Tabs_Icon_Label));
|
||||
internal static string Tabs_Icon_HelpMarker => Get(nameof(Tabs_Icon_HelpMarker));
|
||||
internal static string Tabs_Icon_DefaultOption => Get(nameof(Tabs_Icon_DefaultOption));
|
||||
|
||||
// Hellion Chat — v0.6.0 chat colour presets (display labels)
|
||||
internal static string ChatColourPresets_Default => Get(nameof(ChatColourPresets_Default));
|
||||
internal static string ChatColourPresets_HighContrast => Get(nameof(ChatColourPresets_HighContrast));
|
||||
internal static string ChatColourPresets_Pastell => Get(nameof(ChatColourPresets_Pastell));
|
||||
internal static string ChatColourPresets_DarkModeTuned => Get(nameof(ChatColourPresets_DarkModeTuned));
|
||||
internal static string ChatColourPresets_Hellion => Get(nameof(ChatColourPresets_Hellion));
|
||||
internal static string ChatColourPresets_NightBlue => Get(nameof(ChatColourPresets_NightBlue));
|
||||
internal static string ChatColourPresets_IndigoViolet => Get(nameof(ChatColourPresets_IndigoViolet));
|
||||
|
||||
// Hellion Chat — v0.6.0 chat colour presets section copy
|
||||
internal static string Settings_Appearance_Colours_PresetsHint => Get(nameof(Settings_Appearance_Colours_PresetsHint));
|
||||
|
||||
// Hellion Chat — v0.6.0 pop-out input master switch
|
||||
internal static string Settings_Window_PopOutInputEnabled_Name => Get(nameof(Settings_Window_PopOutInputEnabled_Name));
|
||||
internal static string Settings_Window_PopOutInputEnabled_Description => Get(nameof(Settings_Window_PopOutInputEnabled_Description));
|
||||
|
||||
// Hellion Chat — Window position recovery (off-screen safety net)
|
||||
internal static string Settings_Window_ResetPosition_Name => Get(nameof(Settings_Window_ResetPosition_Name));
|
||||
internal static string Settings_Window_ResetPosition_Description => Get(nameof(Settings_Window_ResetPosition_Description));
|
||||
|
||||
// Hellion Chat — v0.6.0 one-time hint banner shown inside pop-outs
|
||||
internal static string Popout_v060_HintText => Get(nameof(Popout_v060_HintText));
|
||||
internal static string Popout_v060_HintAck => Get(nameof(Popout_v060_HintAck));
|
||||
internal static string Popout_v060_HintOpenSettings => Get(nameof(Popout_v060_HintOpenSettings));
|
||||
|
||||
// Hellion Chat — v0.6.1 pop-out header hint banner (discoverability)
|
||||
internal static string Hint_v061_PopOutHeader_Body => Get(nameof(Hint_v061_PopOutHeader_Body));
|
||||
internal static string Hint_v061_PopOutHeader_Ack => Get(nameof(Hint_v061_PopOutHeader_Ack));
|
||||
internal static string Hint_v061_PopOutHeader_OpenSettings => Get(nameof(Hint_v061_PopOutHeader_OpenSettings));
|
||||
|
||||
// Hellion Chat — v1.0.0 Chat 2 parallel-load conflict detection
|
||||
internal static string ChatTwoConflictTitle => Get(nameof(ChatTwoConflictTitle));
|
||||
internal static string ChatTwoConflictBody => Get(nameof(ChatTwoConflictBody));
|
||||
internal static string ChatTwoConflictAction => Get(nameof(ChatTwoConflictAction));
|
||||
|
||||
// Hellion Chat — v1.2.0 Bottom-Status-Bar Privacy-Badge labels
|
||||
internal static string StatusBar_Privacy_Enabled => Get(nameof(StatusBar_Privacy_Enabled));
|
||||
internal static string StatusBar_Privacy_Open => Get(nameof(StatusBar_Privacy_Open));
|
||||
|
||||
// Hellion Chat — v1.2.0 Appearance / Compact-Density toggle
|
||||
internal static string Appearance_UseCompactDensity_Name => Get(nameof(Appearance_UseCompactDensity_Name));
|
||||
internal static string Appearance_UseCompactDensity_Description => Get(nameof(Appearance_UseCompactDensity_Description));
|
||||
|
||||
// Hellion Chat — v1.2.1 Settings Cleanup: new card titles + subtexts
|
||||
internal static string Settings_Card_ThemeAndLayout_Title => Get(nameof(Settings_Card_ThemeAndLayout_Title));
|
||||
internal static string Settings_Card_ThemeAndLayout_Subtext => Get(nameof(Settings_Card_ThemeAndLayout_Subtext));
|
||||
internal static string Settings_Card_FontsAndColours_Title => Get(nameof(Settings_Card_FontsAndColours_Title));
|
||||
internal static string Settings_Card_FontsAndColours_Subtext => Get(nameof(Settings_Card_FontsAndColours_Subtext));
|
||||
internal static string Settings_Card_DataManagement_Title => Get(nameof(Settings_Card_DataManagement_Title));
|
||||
internal static string Settings_Card_DataManagement_Subtext => Get(nameof(Settings_Card_DataManagement_Subtext));
|
||||
|
||||
// Hellion Chat — v1.2.1 Theme & Layout tab section headings + WindowOpacity slider
|
||||
internal static string Settings_ThemeAndLayout_Theme_Heading => Get(nameof(Settings_ThemeAndLayout_Theme_Heading));
|
||||
internal static string Settings_ThemeAndLayout_WindowStyle_Heading => Get(nameof(Settings_ThemeAndLayout_WindowStyle_Heading));
|
||||
internal static string Settings_ThemeAndLayout_TimestampStyle_Heading => Get(nameof(Settings_ThemeAndLayout_TimestampStyle_Heading));
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacity_Name => Get(nameof(Settings_ThemeAndLayout_WindowOpacity_Name));
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacity_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacity_Description));
|
||||
|
||||
// Hellion Chat — v1.2.1 Fonts & Colours tab section headings
|
||||
internal static string Settings_FontsAndColours_Fonts_Heading => Get(nameof(Settings_FontsAndColours_Fonts_Heading));
|
||||
internal static string Settings_FontsAndColours_Colours_Heading => Get(nameof(Settings_FontsAndColours_Colours_Heading));
|
||||
|
||||
// Hellion Chat — v1.2.1 Data Management tab section headings
|
||||
internal static string Settings_DataManagement_Storage_Heading => Get(nameof(Settings_DataManagement_Storage_Heading));
|
||||
internal static string Settings_DataManagement_Retention_Heading => Get(nameof(Settings_DataManagement_Retention_Heading));
|
||||
internal static string Settings_DataManagement_Cleanup_Heading => Get(nameof(Settings_DataManagement_Cleanup_Heading));
|
||||
internal static string Settings_DataManagement_Export_Heading => Get(nameof(Settings_DataManagement_Export_Heading));
|
||||
internal static string Settings_DataManagement_DbViewer_Heading => Get(nameof(Settings_DataManagement_DbViewer_Heading));
|
||||
internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading));
|
||||
|
||||
// Hellion Chat — v1.2.1 Window-tab Behaviour heading (replaces Frame heading)
|
||||
internal static string Settings_Window_Frame_Behaviour_Heading => Get(nameof(Settings_Window_Frame_Behaviour_Heading));
|
||||
|
||||
// Hellion Chat — v1.2.1 Migration v15 → v16 toast
|
||||
internal static string Migration_v16_OverrideStyle_Toast => Get(nameof(Migration_v16_OverrideStyle_Toast));
|
||||
}
|
||||
+376
-23
@@ -19,11 +19,14 @@
|
||||
<value>Datenschutz-Filter aktivieren</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Description" xml:space="preserve">
|
||||
<value>Wenn aktiviert, werden nur Nachrichten aus den erlaubten Kanälen in die Datenbank gespeichert. Beim Deaktivieren gilt wieder das Standard-Verhalten von ChatTwo, also alles außer Battle-Logs wird gespeichert.</value>
|
||||
<value>Wenn aktiviert, werden nur Nachrichten aus den erlaubten Kanälen in die Datenbank gespeichert. Beim Deaktivieren gilt wieder das Standardverhalten, also alles außer Battle-Logs wird gespeichert.</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>Der Filter steuert nur, was in die lokale Datenbank geschrieben wird. Im Chat-Log siehst du weiterhin jede Nachricht live, ausgeschlossene Kanäle werden nur nicht mehr gespeichert. Wenn du Kanäle auch aus der sichtbaren Anzeige entfernen willst, nutze die normalen Chat-Tab-Filter im Spiel.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy-Filter und Whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Wähle aus, welche Kanäle in die lokale Datenbank gespeichert werden. Standard nach Datensparsamkeit: nur deine eigenen Konversationen. Über die Buttons unten kannst du eine Voreinstellung anwenden.</value>
|
||||
</data>
|
||||
@@ -75,6 +78,12 @@
|
||||
<data name="Cleanup_Help_SavedNote" xml:space="preserve">
|
||||
<value>Das Aufräumen nutzt deine GESPEICHERTE Whitelist (Plugin.Config), nicht ungespeicherte Änderungen oben. Klicke zuerst Speichern, wenn du deine aktuellen Änderungen anwenden willst.</value>
|
||||
</data>
|
||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||
<value>Der manuelle Lauf nutzt deine GESPEICHERTE Retention-Policy, nicht die Slider-Werte oben. Klicke zuerst Speichern, wenn der Lauf deine aktuellen Änderungen anwenden soll.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Vorschau veraltet, deine Whitelist hat sich seit dem letzten Aktualisieren geändert. Klicke Aktualisieren, um neu zu berechnen.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Vorschau aktualisieren</value>
|
||||
</data>
|
||||
@@ -174,18 +183,6 @@
|
||||
<data name="Retention_Error" xml:space="preserve">
|
||||
<value>Aufbewahrungs-Bereinigung fehlgeschlagen, siehe /xllog</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Title" xml:space="preserve">
|
||||
<value>Hellion Chat</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Datenschutz-Filter ist standardmäßig aktiviert. Einstellungen → Datenschutz zum Anpassen.</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Title" xml:space="preserve">
|
||||
<value>Hellion Chat 0.2.0</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Content" xml:space="preserve">
|
||||
<value>Das Webinterface wurde in dieser Version entfernt, weil es nicht auf das Datenschutz-Niveau gehärtet werden konnte das Hellion Chat standardmäßig zusichert. Falls du es genutzt hast, schau bitte in die README für Hintergründe.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Willkommen</value>
|
||||
</data>
|
||||
@@ -214,7 +211,7 @@
|
||||
<value>Volle Historie</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Description" xml:space="preserve">
|
||||
<value>Deaktiviert den Datenschutz-Filter komplett. Speichert alles außer Battle-Logs, wie das Original-Chat 2. Aufbewahrung ist AUS, die Historie wächst dauerhaft.</value>
|
||||
<value>Deaktiviert den Datenschutz-Filter komplett. Speichert alles außer Battle-Logs (das ursprüngliche Voll-Historie-Verhalten). Aufbewahrung ist AUS, die Historie wächst dauerhaft.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_GdprWarning" xml:space="preserve">
|
||||
<value>DSGVO-Hinweis: Wenn du Nachrichten Dritter (Sagen/Schreien/Rufen fremder Spieler, NPC-Dialoge mit Spielernamen usw.) zeitlich unbegrenzt speicherst, kann das die Ausnahme für rein persönliche oder familiäre Tätigkeiten (Art. 2 Abs. 2 Buchst. c) sprengen. Nutze dieses Profil nur, wenn du einen klaren Grund hast, das volle Archiv zu behalten.</value>
|
||||
@@ -273,9 +270,6 @@
|
||||
<data name="Export_Error" xml:space="preserve">
|
||||
<value>Export fehlgeschlagen, siehe /xllog</value>
|
||||
</data>
|
||||
<data name="Theme_Heading" xml:space="preserve">
|
||||
<value>Erscheinungsbild</value>
|
||||
</data>
|
||||
<data name="Theme_Enabled_Name" xml:space="preserve">
|
||||
<value>Hellion-Theme für alle Plugin-Fenster verwenden</value>
|
||||
</data>
|
||||
@@ -368,12 +362,6 @@
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Runtime-Strings) -->
|
||||
<data name="AutoTellTabs_Migration_Title" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_Migration_Content" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs sind ab Version 0.4.0 standardmäßig aktiv. Du kannst sie im Chat-Tab deaktivieren oder anpassen.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_SectionHeader" xml:space="preserve">
|
||||
<value>Aktive Tells</value>
|
||||
</data>
|
||||
@@ -418,6 +406,12 @@
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren — der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Neue /tell-Tabs direkt als Pop-Out öffnen</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Description" xml:space="preserve">
|
||||
<value>Wenn aktiv, wird jeder neu angelegte /tell-Tab sofort als eigenes Fenster geöffnet. Beim Schließen des Fensters kehrt der Tab in die Seitenleiste zurück.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_PreloadHint" xml:space="preserve">
|
||||
<value>Die Anzahl der vorgeladenen Tells lässt sich im Datenschutz-Tab einstellen.</value>
|
||||
</data>
|
||||
@@ -438,4 +432,363 @@
|
||||
<data name="Privacy_AutoTellTabs_Preload_Hint" xml:space="preserve">
|
||||
<value>Greift nur, wenn Auto-Tell-Tabs im Chat-Tab aktiviert sind.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish v10 Wipe-Migration -->
|
||||
<data name="SettingsRefactor_Migration_Title" xml:space="preserve">
|
||||
<value>Settings umstrukturiert</value>
|
||||
</data>
|
||||
<data name="SettingsRefactor_Migration_Content" xml:space="preserve">
|
||||
<value>Hellion Chat 0.5.0 hat die Settings in thematische Tabs umstrukturiert. Deine Chat-Datenbank und dein Nachrichtenverlauf bleiben unverändert. Settings wurden auf Defaults zurückgesetzt. Falls du das Privacy-Profil neu wählen willst, findest du den Reopen-Button im Datenschutz-Tab. Ein Backup der vorherigen Config liegt unter HellionChat.json.pre-v10-backup neben der aktiven Config-Datei.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish 8-Tab-Struktur -->
|
||||
<data name="Settings_Tab_General" xml:space="preserve">
|
||||
<value>Allgemein</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Appearance" xml:space="preserve">
|
||||
<value>Aussehen</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Window" xml:space="preserve">
|
||||
<value>Fenster</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||
<value>Kanäle</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||
<value>Datenbank</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||
<value>Über</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Allgemein-Tabs -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Eingabe</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & Benachrichtigungen</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Sprache & Eingabe-Hilfen</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Aussehen-Tabs -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Fonts_Heading" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat-Farben</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Timestamps_Heading" xml:space="preserve">
|
||||
<value>Zeitstempel</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Fenster-Tabs -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inaktivitäts-Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Fenster-Rahmen</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Chat-Tabs -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Nachrichten-Verhalten</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Vorschau</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Database-Tabs -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Viewer_Heading" xml:space="preserve">
|
||||
<value>Übersicht</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Stats_Heading" xml:space="preserve">
|
||||
<value>Wartung</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Information-Tabs -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Versionsinfo</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>Über HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default-Tab-Presets (kanalspezifisch) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_FreeCompany" xml:space="preserve">
|
||||
<value>Free Company</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Party" xml:space="preserve">
|
||||
<value>Gruppe</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Beginner" xml:space="preserve">
|
||||
<value>Neulinge</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell" xml:space="preserve">
|
||||
<value>Linkshell</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||
<value>Wenn du mehrere Linkshells benutzt, empfiehlt der Maintainer einen Tab pro Shell für eine sauberere Übersicht. Tab duplizieren und je Kopie die Kanalauswahl einschränken.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — v1.2.0 per-tab icon override -->
|
||||
<data name="Tabs_Icon_Label" xml:space="preserve">
|
||||
<value>Tab-Icon</value>
|
||||
</data>
|
||||
<data name="Tabs_Icon_HelpMarker" xml:space="preserve">
|
||||
<value>FontAwesome-Glyph für die Sidebar. Default greift auf Tab-Name oder Channel-Typ.</value>
|
||||
</data>
|
||||
<data name="Tabs_Icon_DefaultOption" xml:space="preserve">
|
||||
<value>(Default-Mapping)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||
<value>Klassik (Chat 2 Default)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||
<value>Hoher Kontrast</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Pastell" xml:space="preserve">
|
||||
<value>Pastell</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_DarkModeTuned" xml:space="preserve">
|
||||
<value>Dunkelmodus-optimiert</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Hellion" xml:space="preserve">
|
||||
<value>Hellion</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_NightBlue" xml:space="preserve">
|
||||
<value>Night Blue</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_IndigoViolet" xml:space="preserve">
|
||||
<value>Indigo Violet</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_PresetsHint" xml:space="preserve">
|
||||
<value>Tipp: Presets überschreiben deine aktuellen Channel-Farben sofort.</value>
|
||||
</data>
|
||||
<data name="Settings_Window_PopOutInputEnabled_Name" xml:space="preserve">
|
||||
<value>Eingabe in Pop-Outs aktivieren</value>
|
||||
</data>
|
||||
<data name="Settings_Window_PopOutInputEnabled_Description" xml:space="preserve">
|
||||
<value>Master-Switch: erlaubt direktes Tippen und Absenden in jedem Pop-Out-Fenster (inkl. Auto-Tell-Tabs). Channel-Wechsel im Pop-Out wirkt global wie im Hauptfenster; Text-Buffer und History-Cursor sind pro Pop-Out unabhängig.</value>
|
||||
</data>
|
||||
<data name="Settings_Window_ResetPosition_Name" xml:space="preserve">
|
||||
<value>Fenster-Position zurücksetzen</value>
|
||||
</data>
|
||||
<data name="Settings_Window_ResetPosition_Description" xml:space="preserve">
|
||||
<value>Holt das Chat-Fenster und alle aktiven Pop-Outs zurück in die linke obere Ecke des Hauptmonitors. Hilfreich wenn ein Fenster nach einem Display-Layout-Wechsel außerhalb des sichtbaren Bereichs gelandet ist (Monitor abgezogen, Auflösung geändert). Das Plugin macht außerdem einmal pro Session einen automatischen Bounds-Check, dieser Button ist der manuelle Notausgang falls trotzdem etwas unerreichbar bleibt.</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintText" xml:space="preserve">
|
||||
<value>Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Master-Switch in den Fenster-Settings aktivieren.</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintAck" xml:space="preserve">
|
||||
<value>Verstanden</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintOpenSettings" xml:space="preserve">
|
||||
<value>Fenster-Settings öffnen</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_Body" xml:space="preserve">
|
||||
<value>Du kannst jeden Chat-Tab als eigenes Fenster öffnen. Klicke auf das Fenster-Symbol oben rechts oder rechtsklicke den Tab. Neu in v0.6.1: die Pop-Out-Eingabe ist standardmäßig aktiv (abschaltbar unter Einstellungen → Fenster).</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_Ack" xml:space="preserve">
|
||||
<value>Verstanden</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_OpenSettings" xml:space="preserve">
|
||||
<value>Einstellungen öffnen</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictTitle" xml:space="preserve">
|
||||
<value>Hellion Chat kann nicht starten, solange Chat 2 geladen ist.</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictBody" xml:space="preserve">
|
||||
<value>Hellion Chat ist ein eigenständiger Fork von Chat 2. Beide Plugins ersetzen dasselbe Chat-Fenster im Spiel und würden zur Laufzeit kollidieren.</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictAction" xml:space="preserve">
|
||||
<value>Chat 2 in /xlplugins deaktivieren, danach Hellion Chat erneut aktivieren.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_General_Title" xml:space="preserve">
|
||||
<value>Allgemein</value>
|
||||
</data>
|
||||
<data name="Settings_Card_General_Subtext" xml:space="preserve">
|
||||
<value>Plugin-globale Einstellungen — Sprache, Eingabe, Audio, Performance.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Appearance_Title" xml:space="preserve">
|
||||
<value>Erscheinungsbild</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Appearance_Subtext" xml:space="preserve">
|
||||
<value>Fensterdeckkraft, Schriften, Bewegung</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Themes_Title" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Themes_Subtext" xml:space="preserve">
|
||||
<value>Theme wählen oder eigenes importieren</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Window_Title" xml:space="preserve">
|
||||
<value>Fenster</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Window_Subtext" xml:space="preserve">
|
||||
<value>Verhalten des Fensters — wann es da ist, ob es bewegt werden kann.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Chat_Title" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Chat_Subtext" xml:space="preserve">
|
||||
<value>Wie Nachrichten angezeigt werden — Tells, Vorschau, Verhalten, Emotes.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Tabs_Title" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Tabs_Subtext" xml:space="preserve">
|
||||
<value>Tab-Verwaltung — eigene Chat-Tabs anlegen und konfigurieren.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Title" xml:space="preserve">
|
||||
<value>Datenschutz</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Subtext" xml:space="preserve">
|
||||
<value>Was darf gespeichert werden — Privacy-Filter pro Channel.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Title" xml:space="preserve">
|
||||
<value>Datenbank</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Subtext" xml:space="preserve">
|
||||
<value>Speicher, Migration, alte Bereinigung</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Title" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Subtext" xml:space="preserve">
|
||||
<value>Über das Plugin — Version, Mission, Lizenz, Changelog.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Themes" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_Active" xml:space="preserve">
|
||||
<value>Aktiv: {0}</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_BuiltIns" xml:space="preserve">
|
||||
<value>Eingebaute Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_Custom" xml:space="preserve">
|
||||
<value>Eigene Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_OpenFolder" xml:space="preserve">
|
||||
<value>Themes-Ordner öffnen</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ExportActive" xml:space="preserve">
|
||||
<value>Aktives exportieren...</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Hint" xml:space="preserve">
|
||||
<value>Dieses Theme schlägt eigene Channel-Farben vor.</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Apply" xml:space="preserve">
|
||||
<value>Übernehmen</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Keep" xml:space="preserve">
|
||||
<value>Behalten</value>
|
||||
</data>
|
||||
<data name="StatusBar_Privacy_Enabled" xml:space="preserve">
|
||||
<value>Privacy-First</value>
|
||||
</data>
|
||||
<data name="StatusBar_Privacy_Open" xml:space="preserve">
|
||||
<value>Offen</value>
|
||||
</data>
|
||||
<data name="Appearance_UseCompactDensity_Name" xml:space="preserve">
|
||||
<value>Kompakte Dichte</value>
|
||||
</data>
|
||||
<data name="Appearance_UseCompactDensity_Description" xml:space="preserve">
|
||||
<value>Schaltet das Message-Layout vom Card-Row-Default zurück auf einzeilige `[HH:mm] Sender: Text` Zeilen.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Title" xml:space="preserve">
|
||||
<value>Theme & Layout</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Subtext" xml:space="preserve">
|
||||
<value>Wie das Fenster aussieht — Theme, Rahmen, Zeitstempel-Style.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Title" xml:space="preserve">
|
||||
<value>Schriften & Farben</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Subtext" xml:space="preserve">
|
||||
<value>Lesbarkeit — Schriftart, Schriftgröße, Chat-Farben pro Channel.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Title" xml:space="preserve">
|
||||
<value>Daten-Verwaltung</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Subtext" xml:space="preserve">
|
||||
<value>Was passiert mit gespeicherten Daten — Aufbewahrung, Aufräumen, Export, DB-Stats.</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowStyle_Heading" xml:space="preserve">
|
||||
<value>Fenster-Style</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_TimestampStyle_Heading" xml:space="preserve">
|
||||
<value>Zeitstempel-Style</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Name" xml:space="preserve">
|
||||
<value>Fenster-Transparenz</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>Wie durchsichtig der Fensterhintergrund ist. Niedrigere Werte lassen mehr vom Spiel durchscheinen. Tipp: Dalamud's Per-Window-Menü (Hamburger in der Titelleiste) bietet pro Fenster eigene Overrides für Deckkraft, Hintergrund-Blur, Durchklick und Anpinnen — die haben Vorrang über diesen Slider für das jeweilige Fenster.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat-Farben</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Storage_Heading" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Retention_Heading" xml:space="preserve">
|
||||
<value>Aufbewahrung</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Cleanup_Heading" xml:space="preserve">
|
||||
<value>Cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Export_Heading" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_DbViewer_Heading" xml:space="preserve">
|
||||
<value>Datenbank-Viewer</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Advanced_Heading" xml:space="preserve">
|
||||
<value>Erweitert (Shift+Klick zum Öffnen)</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Verhalten</value>
|
||||
</data>
|
||||
<data name="Migration_v16_OverrideStyle_Toast" xml:space="preserve">
|
||||
<value>Hellion Chat 1.2.1 hat das Settings-Menü neu sortiert und die alte „Stilüberschreiben"-Option entfernt (überholt durch das Theme-System aus 1.1.0). Deine restlichen Einstellungen bleiben unverändert. Die Fenster-Transparenz ist nach „Theme & Layout" migriert. Ein Backup der vorherigen Config liegt unter pluginConfigs/HellionChat.json.pre-v16-backup neben der aktiven HellionChat.json.</value>
|
||||
</data>
|
||||
</root>
|
||||
+376
-23
@@ -19,11 +19,14 @@
|
||||
<value>Enable privacy filter</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Description" xml:space="preserve">
|
||||
<value>When enabled, only messages from whitelisted channels are persisted to the database. Disabling restores upstream ChatTwo behavior (everything except battle messages is stored).</value>
|
||||
<value>When enabled, only messages from whitelisted channels are persisted to the database. Disabling restores the original behavior (everything except battle messages is stored).</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>The filter only controls what is written to the local database. The chat log itself keeps showing every message live, disallowed channels just stop being saved. Use the channel hide options in your in-game chat tabs if you want to remove channels from the visible chat.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy filter and whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Pick which channels are stored in the local database. Privacy-First default: only your own conversations. Use the buttons below to apply a preset.</value>
|
||||
</data>
|
||||
@@ -75,6 +78,12 @@
|
||||
<data name="Cleanup_Help_SavedNote" xml:space="preserve">
|
||||
<value>Cleanup uses your SAVED whitelist (Plugin.Config), not unsaved edits above. Click Save first if you want to apply your current edits.</value>
|
||||
</data>
|
||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||
<value>The manual sweep uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current edits.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Preview is out of date — your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Refresh preview</value>
|
||||
</data>
|
||||
@@ -174,18 +183,6 @@
|
||||
<data name="Retention_Error" xml:space="preserve">
|
||||
<value>Retention sweep failed, see /xllog</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Title" xml:space="preserve">
|
||||
<value>Hellion Chat</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Privacy filter activated by default. Settings → Privacy to adjust.</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Title" xml:space="preserve">
|
||||
<value>Hellion Chat 0.2.0</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Content" xml:space="preserve">
|
||||
<value>The webinterface has been removed in this version because it could not be hardened to the privacy guarantees Hellion Chat makes by default. If you used it, please consult the README for context.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Welcome</value>
|
||||
</data>
|
||||
@@ -214,7 +211,7 @@
|
||||
<value>Full History</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_Description" xml:space="preserve">
|
||||
<value>Disables the privacy filter entirely. Stores everything except battle logs, just like upstream Chat 2. Retention is OFF, history grows forever.</value>
|
||||
<value>Disables the privacy filter entirely. Stores everything except battle logs (the original full-history behavior). Retention is OFF, history grows forever.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_FullHistory_GdprWarning" xml:space="preserve">
|
||||
<value>GDPR notice: storing third-party messages (Say/Shout/Yell of strangers, NPC dialogue with player names, etc.) for an unlimited time may exceed the personal/household exemption (Art. 2(2)(c)). Use this profile only if you have a clear reason to keep the full archive.</value>
|
||||
@@ -273,9 +270,6 @@
|
||||
<data name="Export_Error" xml:space="preserve">
|
||||
<value>Export failed, see /xllog</value>
|
||||
</data>
|
||||
<data name="Theme_Heading" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Theme_Enabled_Name" xml:space="preserve">
|
||||
<value>Use the Hellion theme across all plugin windows</value>
|
||||
</data>
|
||||
@@ -368,12 +362,6 @@
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (runtime strings) -->
|
||||
<data name="AutoTellTabs_Migration_Title" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_Migration_Content" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs are enabled by default starting with version 0.4.0. You can disable or fine-tune them in the Chat tab.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_SectionHeader" xml:space="preserve">
|
||||
<value>Active Tells</value>
|
||||
</data>
|
||||
@@ -418,6 +406,12 @@
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Adds a click-to-toggle button next to each auto tell tab to mark a partner as already greeted, dimming the tab name when set. Useful for club greeters tracking many parallel conversations; off by default.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Open new /tell tabs directly as pop-out</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Description" xml:space="preserve">
|
||||
<value>When enabled, each newly created /tell tab opens directly as its own window. Closing the window returns the tab to the sidebar.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_PreloadHint" xml:space="preserve">
|
||||
<value>The number of preloaded tells is configured in the Privacy tab.</value>
|
||||
</data>
|
||||
@@ -438,4 +432,363 @@
|
||||
<data name="Privacy_AutoTellTabs_Preload_Hint" xml:space="preserve">
|
||||
<value>Only takes effect when auto tell tabs are enabled in the Chat tab.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish v10 wipe migration -->
|
||||
<data name="SettingsRefactor_Migration_Title" xml:space="preserve">
|
||||
<value>Settings reorganised</value>
|
||||
</data>
|
||||
<data name="SettingsRefactor_Migration_Content" xml:space="preserve">
|
||||
<value>Hellion Chat 0.5.0 reorganised the settings into themed tabs. Your chat database and your message history stay untouched. Settings have been reset to defaults; if you want to pick a privacy profile again, the reopen button is in the Privacy tab. A backup of your previous config is at HellionChat.json.pre-v10-backup next to the live config file.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish 8-tab structure -->
|
||||
<data name="Settings_Tab_General" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Appearance" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Window" xml:space="preserve">
|
||||
<value>Window</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — General-Tab section headings -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & Notifications</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Language & Input Helpers</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Appearance-Tab section headings -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Fonts_Heading" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat Colours</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Timestamps_Heading" xml:space="preserve">
|
||||
<value>Timestamps</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Window-Tab section headings -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Hide</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inactivity Hide</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Window Frame</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat-Tab section headings -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Message Behaviour</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Database-Tab section headings -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Storage</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Viewer_Heading" xml:space="preserve">
|
||||
<value>Overview</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Stats_Heading" xml:space="preserve">
|
||||
<value>Maintenance</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Information-Tab section headings -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Version Info</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>About HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default tab presets (channel-themed) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_FreeCompany" xml:space="preserve">
|
||||
<value>Free Company</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Party" xml:space="preserve">
|
||||
<value>Party</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Beginner" xml:space="preserve">
|
||||
<value>Beginner</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell" xml:space="preserve">
|
||||
<value>Linkshell</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||
<value>If you use multiple linkshells, the maintainer recommends one tab per shell for cleaner readability. Duplicate this tab and narrow the channel selection per copy.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — v1.2.0 per-tab icon override -->
|
||||
<data name="Tabs_Icon_Label" xml:space="preserve">
|
||||
<value>Tab-Icon</value>
|
||||
</data>
|
||||
<data name="Tabs_Icon_HelpMarker" xml:space="preserve">
|
||||
<value>FontAwesome-Glyph für die Sidebar. Default greift auf Tab-Name oder Channel-Typ.</value>
|
||||
</data>
|
||||
<data name="Tabs_Icon_DefaultOption" xml:space="preserve">
|
||||
<value>(Default-Mapping)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||
<value>Klassik (Chat 2 Default)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||
<value>High-Contrast</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Pastell" xml:space="preserve">
|
||||
<value>Pastell</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_DarkModeTuned" xml:space="preserve">
|
||||
<value>Dark-Mode-Tuned</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Hellion" xml:space="preserve">
|
||||
<value>Hellion</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_NightBlue" xml:space="preserve">
|
||||
<value>Night Blue</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_IndigoViolet" xml:space="preserve">
|
||||
<value>Indigo Violet</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_PresetsHint" xml:space="preserve">
|
||||
<value>Tip: presets overwrite your current channel colours immediately.</value>
|
||||
</data>
|
||||
<data name="Settings_Window_PopOutInputEnabled_Name" xml:space="preserve">
|
||||
<value>Enable input in pop-outs</value>
|
||||
</data>
|
||||
<data name="Settings_Window_PopOutInputEnabled_Description" xml:space="preserve">
|
||||
<value>Master switch: lets you type and send messages directly inside every pop-out window (including auto-tell tabs). Channel changes inside a pop-out apply globally just like in the main window; the text buffer and history cursor stay independent per pop-out.</value>
|
||||
</data>
|
||||
<data name="Settings_Window_ResetPosition_Name" xml:space="preserve">
|
||||
<value>Reset Window Position</value>
|
||||
</data>
|
||||
<data name="Settings_Window_ResetPosition_Description" xml:space="preserve">
|
||||
<value>Snaps the chat window and every active pop-out back to the primary monitor's top-left corner. Useful when a window has drifted off-screen after a display layout change (monitor disconnected, resolution changed). The plugin also runs an automatic bounds check once per session — this button is the manual backup if anything still ends up unreachable.</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintText" xml:space="preserve">
|
||||
<value>New in v0.6.0: you can type directly inside pop-out windows. Toggle the master switch in the window settings to enable it.</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintAck" xml:space="preserve">
|
||||
<value>Got it</value>
|
||||
</data>
|
||||
<data name="Popout_v060_HintOpenSettings" xml:space="preserve">
|
||||
<value>Open window settings</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_Body" xml:space="preserve">
|
||||
<value>You can open any chat tab as its own window. Click the window icon in the top right or right-click the tab. New in v0.6.1: pop-out input is enabled by default (can be turned off under Settings → Window).</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_Ack" xml:space="preserve">
|
||||
<value>Got it</value>
|
||||
</data>
|
||||
<data name="Hint_v061_PopOutHeader_OpenSettings" xml:space="preserve">
|
||||
<value>Open Settings</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictTitle" xml:space="preserve">
|
||||
<value>Hellion Chat cannot start while Chat 2 is loaded.</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictBody" xml:space="preserve">
|
||||
<value>Hellion Chat is a standalone fork of Chat 2. Both plugins replace the same in-game chat window and would conflict at runtime if loaded together.</value>
|
||||
</data>
|
||||
<data name="ChatTwoConflictAction" xml:space="preserve">
|
||||
<value>Disable Chat 2 in /xlplugins, then re-enable Hellion Chat.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_General_Title" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Settings_Card_General_Subtext" xml:space="preserve">
|
||||
<value>Plugin-wide settings — language, input, audio, performance.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Appearance_Title" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Appearance_Subtext" xml:space="preserve">
|
||||
<value>Window opacity, fonts, motion</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Themes_Title" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Themes_Subtext" xml:space="preserve">
|
||||
<value>Choose a theme or import your own</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Window_Title" xml:space="preserve">
|
||||
<value>Window</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Window_Subtext" xml:space="preserve">
|
||||
<value>Window behaviour — when it shows, whether it can move.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Chat_Title" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Chat_Subtext" xml:space="preserve">
|
||||
<value>How messages are displayed — tells, preview, behaviour, emotes.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Tabs_Title" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Tabs_Subtext" xml:space="preserve">
|
||||
<value>Tab management — create and configure your own chat tabs.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Title" xml:space="preserve">
|
||||
<value>Privacy</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Subtext" xml:space="preserve">
|
||||
<value>What's allowed to be stored — privacy filter per channel.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Title" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Subtext" xml:space="preserve">
|
||||
<value>Storage, migration, legacy cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Title" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Subtext" xml:space="preserve">
|
||||
<value>About the plugin — version, mission, license, changelog.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Themes" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_Active" xml:space="preserve">
|
||||
<value>Active: {0}</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_BuiltIns" xml:space="preserve">
|
||||
<value>Built-in themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_Custom" xml:space="preserve">
|
||||
<value>Custom themes</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_OpenFolder" xml:space="preserve">
|
||||
<value>Open themes folder</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ExportActive" xml:space="preserve">
|
||||
<value>Export active...</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Hint" xml:space="preserve">
|
||||
<value>This theme suggests its own chat channel colours.</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Apply" xml:space="preserve">
|
||||
<value>Apply</value>
|
||||
</data>
|
||||
<data name="Settings_Themes_ApplyChatColors_Keep" xml:space="preserve">
|
||||
<value>Keep current</value>
|
||||
</data>
|
||||
<data name="StatusBar_Privacy_Enabled" xml:space="preserve">
|
||||
<value>Privacy-First</value>
|
||||
</data>
|
||||
<data name="StatusBar_Privacy_Open" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="Appearance_UseCompactDensity_Name" xml:space="preserve">
|
||||
<value>Compact Density</value>
|
||||
</data>
|
||||
<data name="Appearance_UseCompactDensity_Description" xml:space="preserve">
|
||||
<value>Schaltet das Message-Layout vom Card-Row-Default zurück auf einzeilige `[HH:mm] Sender: Text` Zeilen.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Title" xml:space="preserve">
|
||||
<value>Theme & Layout</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Subtext" xml:space="preserve">
|
||||
<value>How the window looks — theme, frame, timestamp style.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Title" xml:space="preserve">
|
||||
<value>Fonts & Colours</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Subtext" xml:space="preserve">
|
||||
<value>Readability — font, font size, per-channel chat colours.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Title" xml:space="preserve">
|
||||
<value>Data Management</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Subtext" xml:space="preserve">
|
||||
<value>What happens to stored data — retention, cleanup, export, DB stats.</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowStyle_Heading" xml:space="preserve">
|
||||
<value>Window Style</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_TimestampStyle_Heading" xml:space="preserve">
|
||||
<value>Timestamp Style</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Name" xml:space="preserve">
|
||||
<value>Window Transparency</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>How transparent the window background is. Lower values let the game show through more. Tip: Dalamud's per-window menu (Hamburger in the title bar) gives you per-window overrides for opacity, background blur, click-through and pinning — those override this slider for that window.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat Colours</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Storage_Heading" xml:space="preserve">
|
||||
<value>Storage</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Retention_Heading" xml:space="preserve">
|
||||
<value>Retention</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Cleanup_Heading" xml:space="preserve">
|
||||
<value>Cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Export_Heading" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_DbViewer_Heading" xml:space="preserve">
|
||||
<value>Database Viewer</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Advanced_Heading" xml:space="preserve">
|
||||
<value>Advanced (Shift+Click to open)</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Behaviour</value>
|
||||
</data>
|
||||
<data name="Migration_v16_OverrideStyle_Toast" xml:space="preserve">
|
||||
<value>Hellion Chat 1.2.1 reorganised the Settings menu and removed the legacy "Style override" option (made obsolete by the Themes system in 1.1.0). Your other settings are unchanged. Window opacity was migrated to Theme & Layout. A backup of your previous config is at pluginConfigs/HellionChat.json.pre-v16-backup next to the live HellionChat.json.</value>
|
||||
</data>
|
||||
</root>
|
||||
+38
-2
@@ -7,7 +7,7 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ChatTwo.Resources {
|
||||
namespace HellionChat.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace ChatTwo.Resources {
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ChatTwo.Resources.Language", typeof(Language).Assembly);
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HellionChat.Resources.Language", typeof(Language).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
@@ -2148,6 +2148,24 @@ namespace ChatTwo.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The channel selector button next to the input field is tinted with the currently active channel's colour. Matches the tinting of the input text itself..
|
||||
/// </summary>
|
||||
internal static string Options_ColorSelectedInputChannelButton_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Options_ColorSelectedInputChannelButton_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Tint channel selector with channel colour.
|
||||
/// </summary>
|
||||
internal static string Options_ColorSelectedInputChannelButton_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Options_ColorSelectedInputChannelButton_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Chat colours.
|
||||
/// </summary>
|
||||
@@ -2661,6 +2679,24 @@ namespace ChatTwo.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide the chat while the New Game+ menu is open. Closing the menu shows the chat again..
|
||||
/// </summary>
|
||||
internal static string Options_HideInNewGamePlusMenu_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Options_HideInNewGamePlusMenu_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide while New Game+ menu is open.
|
||||
/// </summary>
|
||||
internal static string Options_HideInNewGamePlusMenu_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Options_HideInNewGamePlusMenu_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Hide {0} during loading screens..
|
||||
/// </summary>
|
||||
@@ -208,6 +208,12 @@
|
||||
<data name="Options_ChatColours_Import">
|
||||
<value>Vom Spiel importieren</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Channel-Auswahl-Knopf in Channel-Farbe</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Der Channel-Auswahl-Knopf neben dem Eingabefeld bekommt die Farbe des aktuell aktiven Channels. Konsistent zur Färbung des Eingabetextes selbst.</value>
|
||||
</data>
|
||||
<data name="Options_Tabs_Tab">
|
||||
<value>Kanäle</value>
|
||||
</data>
|
||||
@@ -1190,6 +1196,12 @@ Sie wurden gewarnt.</value>
|
||||
<data name="Options_HideInBattle_Description" xml:space="preserve">
|
||||
<value>Blende den Chat während der Kämpfe aus.</value>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Während des New-Game+ Menüs ausblenden</value>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Blendet den Chat aus, solange das New-Game+ Menü geöffnet ist. Schließen des Menüs blendet den Chat wieder ein.</value>
|
||||
</data>
|
||||
<data name="Options_Emote_EmoteStats" xml:space="preserve">
|
||||
<value>Emote-Statistik</value>
|
||||
</data>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user