Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+1
-1
@@ -1,2 +1,2 @@
|
||||
# Generated files
|
||||
ChatTwo/Resources/Language.*.resx linguist-generated=true
|
||||
HellionChat/Resources/Language.*.resx linguist-generated=true
|
||||
@@ -59,7 +59,7 @@ body:
|
||||
id: log
|
||||
attributes:
|
||||
label: Relevant /xllog excerpt
|
||||
description: Filter for "HellionChat" or "ChatTwo" if the log is huge
|
||||
description: Filter for "HellionChat" if the log is huge
|
||||
render: text
|
||||
|
||||
- type: checkboxes
|
||||
|
||||
@@ -53,7 +53,7 @@ new commands, new translations, removed behaviour. If none, write
|
||||
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 UPSTREAM_SYNC.md.
|
||||
- Does this affect the upstream cherry-pick path? See docs/UPSTREAM_SYNC.md.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
@@ -67,6 +67,6 @@ new commands, new translations, removed behaviour. If none, write
|
||||
- [ ] 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](../AI_DISCLOSURE.md)).
|
||||
in the PR description (see [AI_DISCLOSURE.md](../docs/AI_DISCLOSURE.md)).
|
||||
- [ ] I confirm my contribution is released under the
|
||||
[EUPL-1.2](../LICENSE).
|
||||
|
||||
@@ -5,7 +5,7 @@ updates:
|
||||
# noise down while still catching transitive security advisories within
|
||||
# a few days of disclosure.
|
||||
- package-ecosystem: nuget
|
||||
directory: /ChatTwo
|
||||
directory: /HellionChat
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
|
||||
@@ -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.
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
@@ -42,15 +42,15 @@ jobs:
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore ChatTwo/ChatTwo.csproj
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release --no-restore
|
||||
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: ChatTwo/bin/Release/**/HellionChat/**
|
||||
path: HellionChat/bin/Release/**/HellionChat/**
|
||||
if-no-files-found: warn
|
||||
retention-days: 14
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
@@ -55,20 +55,20 @@ jobs:
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: csharp
|
||||
build-mode: manual
|
||||
queries: security-extended
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore ChatTwo/ChatTwo.csproj
|
||||
run: dotnet restore HellionChat/HellionChat.csproj
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release --no-restore
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release --no-restore
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:csharp
|
||||
|
||||
@@ -82,12 +82,12 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: actions
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:actions
|
||||
|
||||
@@ -8,7 +8,7 @@ name: Release
|
||||
# - 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
|
||||
# ChatTwo/bin/Release derived from Get-ChildItem) or pinned URLs to
|
||||
# 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.
|
||||
|
||||
@@ -16,6 +16,16 @@ 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
|
||||
@@ -27,11 +37,17 @@ jobs:
|
||||
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@v4
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
@@ -44,16 +60,16 @@ jobs:
|
||||
Expand-Archive -Force -Path dalamud.zip -DestinationPath $hooks
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build ChatTwo/ChatTwo.csproj --configuration Release
|
||||
run: dotnet build HellionChat/HellionChat.csproj --configuration Release
|
||||
|
||||
- name: Locate latest.zip
|
||||
id: locate
|
||||
shell: pwsh
|
||||
run: |
|
||||
$zip = Get-ChildItem -Path ChatTwo\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
|
||||
$zip = Get-ChildItem -Path HellionChat\bin\Release -Recurse -Filter latest.zip | Select-Object -First 1
|
||||
if (-not $zip)
|
||||
{
|
||||
throw "latest.zip not found under ChatTwo\bin\Release"
|
||||
throw "latest.zip not found under HellionChat\bin\Release"
|
||||
}
|
||||
Write-Host "Found: $($zip.FullName)"
|
||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
@@ -71,7 +87,12 @@ jobs:
|
||||
- name: Generate release body
|
||||
shell: pwsh
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
# 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+$') {
|
||||
@@ -79,7 +100,7 @@ jobs:
|
||||
}
|
||||
$version = $tag.Substring(1)
|
||||
|
||||
$yamlPath = "ChatTwo/HellionChat.yaml"
|
||||
$yamlPath = "HellionChat/HellionChat.yaml"
|
||||
$raw = Get-Content -Path $yamlPath -Raw
|
||||
|
||||
$marker = "changelog: |-"
|
||||
@@ -112,34 +133,14 @@ jobs:
|
||||
$currentBlock = $rest.TrimEnd()
|
||||
}
|
||||
|
||||
$footer = @'
|
||||
|
||||
---
|
||||
|
||||
## 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/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.
|
||||
'@
|
||||
# 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
|
||||
@@ -152,6 +153,11 @@ also EUPL-1.2.
|
||||
- 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
|
||||
|
||||
+10
-11
@@ -10,7 +10,7 @@ what I am not, and how to make a contribution land smoothly.
|
||||
- Read the [README](README.md) so you understand the scope: this is a
|
||||
privacy-focused, EUPL-1.2-licensed Dalamud plugin that intentionally
|
||||
removes the upstream webinterface and ships smaller defaults.
|
||||
- Read [UPSTREAM_SYNC.md](UPSTREAM_SYNC.md). Cherry-picks from upstream
|
||||
- Read [UPSTREAM_SYNC.md](docs/UPSTREAM_SYNC.md). Cherry-picks from upstream
|
||||
Chat 2 are selective and conscious; not everything that lands there
|
||||
belongs here.
|
||||
- Read [SECURITY.md](SECURITY.md). Anything security-sensitive goes
|
||||
@@ -22,7 +22,7 @@ what I am not, and how to make a contribution land smoothly.
|
||||
- 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 `ChatTwo/Resources/HellionStrings.*.resx`.
|
||||
pull requests against `HellionChat/Resources/HellionStrings.*.resx`.
|
||||
Translations for the upstream Chat 2 strings (`Language.*.resx`) are
|
||||
not handled here; they go through the upstream Chat 2 project.
|
||||
- Documentation improvements (README, comments, this file).
|
||||
@@ -40,7 +40,7 @@ what I am not, and how to make a contribution land smoothly.
|
||||
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
|
||||
[AI_DISCLOSURE.md](AI_DISCLOSURE.md) for how I handle AI assistance
|
||||
[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
|
||||
@@ -60,7 +60,7 @@ proposal than to a finished pull request.
|
||||
easier to review than one big 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 `ChatTwo/HellionChat.yaml` and
|
||||
and/or the changelog block in `HellionChat/HellionChat.yaml` and
|
||||
`repo.json` for the next version. I bump the version number myself
|
||||
at release time, so you do not need to.
|
||||
6. Open the pull request against `main`. The PR template will ask
|
||||
@@ -79,13 +79,12 @@ locally you need:
|
||||
|
||||
```
|
||||
dotnet restore
|
||||
dotnet build ChatTwo.sln -c Release
|
||||
dotnet test ChatTwo.sln -c Release
|
||||
dotnet build HellionChat.sln -c Release
|
||||
```
|
||||
|
||||
The test project is `ChatTwo.Tests`. New behaviour should come with a
|
||||
test where the existing test infrastructure makes that practical
|
||||
(privacy filter, configuration migration, message store).
|
||||
Tests are not part of the current `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 through `/xlplugins`.
|
||||
@@ -114,11 +113,11 @@ There is no separate CLA.
|
||||
|
||||
## Translations
|
||||
|
||||
Hellion-specific strings live in `ChatTwo/Resources/HellionStrings.resx`
|
||||
Hellion-specific strings live in `HellionChat/Resources/HellionStrings.resx`
|
||||
(English source) and `HellionStrings.<lang>.resx` (per-language).
|
||||
Translations are accepted as direct pull requests against those files.
|
||||
|
||||
The upstream Chat 2 strings in `ChatTwo/Resources/Language.*.resx` are
|
||||
The upstream Chat 2 strings in `HellionChat/Resources/Language.*.resx` are
|
||||
**not** translated in this repository. They are owned by the upstream
|
||||
Chat 2 project and synced in via cherry-pick. Please contribute
|
||||
upstream-string translations to
|
||||
|
||||
@@ -1,139 +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
|
||||
icon_url: https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png
|
||||
image_urls:
|
||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/chatWindow.png
|
||||
- https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/withSimpleTweaks.png
|
||||
tags:
|
||||
- Social
|
||||
- UI
|
||||
- Chat
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 0.6.0 — UX Polish: Pop-Out Input + Colour Presets**
|
||||
|
||||
Two opt-in UX features land in the same release. Existing users see
|
||||
no change unless they enable the new toggles.
|
||||
|
||||
Pop-out input bar:
|
||||
|
||||
- New global master switch in Settings → Window → Frame: "Enable input
|
||||
in pop-outs". Default OFF so existing behaviour is preserved
|
||||
- When enabled, every pop-out window grows a compact input bar at the
|
||||
bottom (channel-coloured icon button left, text input right). The
|
||||
auto-translate picker is intentionally not part of the compact bar
|
||||
in v0.6.0 — typical pop-out workflows (FC greeter, club hostess)
|
||||
rarely need it there
|
||||
- Each pop-out keeps an independent text buffer and history cursor;
|
||||
channel changes still apply globally because that is how the FFXIV
|
||||
channel API works
|
||||
- Up/Down navigates a shared input history singleton across the main
|
||||
window and every open pop-out
|
||||
- First pop-out opening after the upgrade shows a one-time hint
|
||||
banner pointing users to the new toggle
|
||||
|
||||
Chat colour presets:
|
||||
|
||||
- Seven built-in presets above the per-channel colour list in
|
||||
Settings → Appearance → Colours: ChatTwo Default, High-Contrast,
|
||||
Pastell, Dark-Mode-Tuned, Hellion (brand-coloured, blue/orange
|
||||
Arctic Cyan + Ember Glow palette from the Hellion Online Media
|
||||
branding spec), plus two bonus mood presets — Night Blue (royal
|
||||
blue, classic-cool) and Indigo Violet (royal violet, glitter-mystic)
|
||||
- Apply is immediate and overwrites the channels covered by the
|
||||
preset; battle-channel colours are left alone so combat tuning
|
||||
stays intact
|
||||
|
||||
Configuration migrates from v10 to v11 with a diagnostic log entry;
|
||||
no data is reset. Bilingual (English/German) for both new sections.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.4 — WrapText hardening**
|
||||
|
||||
Replaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with
|
||||
Span- and index-based control flow. Closes the persistent CodeQL
|
||||
Critical alert "unvalidated local pointer arithmetic" that kept
|
||||
re-firing on every shape of the previous fix.
|
||||
|
||||
Hardening:
|
||||
|
||||
- WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount
|
||||
via ArrayPool, validates the actual encoded length against that
|
||||
ceiling, and threads the rest of the algorithm through int offsets
|
||||
instead of raw byte pointers
|
||||
- Pointer arithmetic only happens inside two small private helpers
|
||||
(CalcWordWrap and DrawText) that take the pinned base pointer plus
|
||||
int offsets sourced from the plugin's own logic, not from any
|
||||
virtual-method return
|
||||
- Added a 16 KiB upper bound on the buffer rent to prevent a
|
||||
pathological input from triggering an unbounded ArrayPool allocation
|
||||
|
||||
No user-visible behaviour change. Word-wrap output is byte-identical
|
||||
to v0.5.3.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.3 — Pointer arithmetic hardening**
|
||||
|
||||
Closed CodeQL Critical alert in ImGuiUtil.WrapText by validating the
|
||||
encoded byte buffer length via GetByteCount before pointer
|
||||
arithmetic. Single-fix patch on top of v0.5.2.
|
||||
|
||||
**Hellion Chat 0.5.2 — Bugfix patch**
|
||||
|
||||
Auto-Tell-Tabs history-separator landed below the live tell instead
|
||||
of above (preload now excludes the trigger message). Plugin icon
|
||||
packaging fixed by removing a stale DalamudPackager.targets override
|
||||
that conflicted with the SDK 15 default. Default config aligned to
|
||||
the maintainer's daily driver: HellionThemeWindowOpacity 0.5,
|
||||
Use24HourClock true, Gruppe tab no longer auto-routes /party. Two
|
||||
earlier CodeQL findings closed (workflow permissions, empty-input
|
||||
pointer arithmetic).
|
||||
|
||||
**Hellion Chat 0.5.1 — Backlog Sweep**
|
||||
|
||||
Pure hardening and polish. Eight backlog items from the v0.5.0
|
||||
codebase review collected into one patch: cleanup-preview-stale
|
||||
detection, greeted-tab dim background, Performance HelpMarker
|
||||
consistency, Tabs/Database tab names from HellionStrings,
|
||||
FontChooser framework-thread marshalling, async-void on
|
||||
EmoteCache.LoadData, parameterised SQL via BindIntList helper.
|
||||
|
||||
---
|
||||
|
||||
Earlier history at https://github.com/JonKazama-Hellion/HellionChat/releases.
|
||||
@@ -1 +0,0 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ChatTwo.Tests")]
|
||||
@@ -1,8 +1,6 @@
|
||||
|
||||
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}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HellionChat", "HellionChat\HellionChat.csproj", "{739F75E6-B65F-41EF-9D90-F7BC519E4875}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -14,9 +12,5 @@ Global
|
||||
{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,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
|
||||
@@ -207,6 +223,17 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -355,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,7 +34,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int LatestVersion = 11;
|
||||
private const int LatestVersion = 12;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -111,9 +111,24 @@ public class Configuration : IPluginConfiguration
|
||||
// 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 OFF so existing users see no behavior
|
||||
// change after the v10→v11 migration.
|
||||
public bool PopOutInputEnabled;
|
||||
// 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)
|
||||
{
|
||||
@@ -130,6 +145,7 @@ public class Configuration : IPluginConfiguration
|
||||
public bool HideWhenUiHidden = true;
|
||||
public bool HideInLoadingScreens;
|
||||
public bool HideInBattle;
|
||||
public bool HideInNewGamePlusMenu;
|
||||
public bool HideWhenInactive;
|
||||
public int InactivityHideTimeout = 10;
|
||||
public bool InactivityHideActiveDuringBattle = true;
|
||||
@@ -206,6 +222,7 @@ public class Configuration : IPluginConfiguration
|
||||
public float TooltipOffset;
|
||||
public float WindowAlpha = 100f;
|
||||
public Dictionary<ChatType, uint> ChatColours = new();
|
||||
public bool ColorSelectedInputChannelButton = true;
|
||||
public List<Tab> Tabs = [];
|
||||
|
||||
public bool OverrideStyle;
|
||||
@@ -226,6 +243,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;
|
||||
@@ -273,6 +291,7 @@ public class Configuration : IPluginConfiguration
|
||||
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
|
||||
@@ -311,6 +330,8 @@ public class Configuration : IPluginConfiguration
|
||||
|
||||
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||
SeenPopOutHeaderHint = other.SeenPopOutHeaderHint;
|
||||
AutoTellTabsOpenAsPopout = other.AutoTellTabsOpenAsPopout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using Dalamud.Bindings.ImGui;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public static class EmoteCache
|
||||
{
|
||||
@@ -192,7 +192,7 @@ public static class EmoteCache
|
||||
}
|
||||
else
|
||||
{
|
||||
var content = await new HttpClient().GetAsync(EmotePath.Format(emote.Id));
|
||||
var content = await Client.GetAsync(EmotePath.Format(emote.Id));
|
||||
RawData = await content.Content.ReadAsByteArrayAsync();
|
||||
|
||||
await using var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
@@ -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
|
||||
{
|
||||
@@ -39,11 +39,18 @@ public class FontManager
|
||||
}
|
||||
else
|
||||
{
|
||||
GameSymFont = new HttpClient().GetAsync("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")
|
||||
.Result
|
||||
.Content
|
||||
.ReadAsByteArrayAsync()
|
||||
.Result;
|
||||
// Dispose HttpClient and HttpResponseMessage to avoid socket
|
||||
// exhaustion on repeated cold-start downloads. GetAwaiter().GetResult()
|
||||
// unwraps AggregateException so failures surface cleanly. A full
|
||||
// async refactor of the constructor would be cleaner but is out of
|
||||
// scope for v1.0.0 — tracked in the backlog.
|
||||
using var client = new HttpClient();
|
||||
using var response = client
|
||||
.GetAsync("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
response.EnsureSuccessStatusCode();
|
||||
GameSymFont = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
|
||||
|
||||
Dalamud.Utility.FilesystemUtil.WriteAllBytesSafe(filePath, GameSymFont);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
+10
-2
@@ -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!;
|
||||
@@ -249,9 +251,15 @@ internal unsafe class GameFunctions : IDisposable
|
||||
|
||||
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);
|
||||
|
||||
MemoryHelper.WriteString(PlaceholderNamePtr, ReplacementName);
|
||||
ReplacementName = null;
|
||||
+5
-5
@@ -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,
|
||||
@@ -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
|
||||
{
|
||||
+4
-1
@@ -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
|
||||
@@ -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;
|
||||
@@ -4,22 +4,31 @@
|
||||
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.6.0</Version>
|
||||
<Version>1.0.2</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. -->
|
||||
<!-- 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>ChatTwo</RootNamespace>
|
||||
<RootNamespace>HellionChat</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<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.3.0" />
|
||||
<PackageReference Include="Pidgin" Version="3.5.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
</ItemGroup>
|
||||
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
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
|
||||
|
||||
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/withSimpleTweaks.png
|
||||
tags:
|
||||
- Social
|
||||
- UI
|
||||
- Chat
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 1.0.2 — 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
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
// Hellion Chat — v0.6.0 shared input history. Replaces the embedded
|
||||
// ChatLogWindow.InputBacklog so that pop-out windows with their own
|
||||
@@ -1,6 +1,6 @@
|
||||
using Dalamud.Plugin.Ipc;
|
||||
|
||||
namespace ChatTwo.Ipc;
|
||||
namespace HellionChat.Ipc;
|
||||
|
||||
public sealed class ExtraChat : IDisposable
|
||||
{
|
||||
@@ -49,6 +49,8 @@ public sealed class ExtraChat : IDisposable
|
||||
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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
@@ -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()));
|
||||
@@ -2,10 +2,10 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ChatTwo.Ipc;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Ipc;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Ui;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.IoC;
|
||||
@@ -14,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
|
||||
@@ -94,6 +94,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();
|
||||
@@ -167,22 +173,85 @@ public sealed class Plugin : IDalamudPlugin
|
||||
"SeenPopOutInputHint added (default false)");
|
||||
}
|
||||
|
||||
// Hellion default tab layout for first-run and v10-wipe.
|
||||
// General catches player chat plus active gameplay events; the
|
||||
// System tab takes the technical noise so it does not bury real
|
||||
// conversation. Beginner tab only appears when the Novice
|
||||
// Network is enabled in Audio and Notifications, otherwise it
|
||||
// would just sit empty.
|
||||
// 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.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (Config.ShowNoviceNetwork)
|
||||
Config.Tabs.Add(TabsUtil.HellionBeginner);
|
||||
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
||||
Config.Tabs.Add(TabsUtil.VanillaTellExclusive);
|
||||
}
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
@@ -502,6 +571,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;
|
||||
@@ -1,6 +1,6 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
|
||||
namespace ChatTwo.Privacy;
|
||||
namespace HellionChat.Privacy;
|
||||
|
||||
internal static class PrivacyDefaults
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace ChatTwo.Resources;
|
||||
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
|
||||
Generated
+18
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace ChatTwo.Resources;
|
||||
namespace HellionChat.Resources;
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
|
||||
@@ -26,7 +26,7 @@ internal class HellionStrings
|
||||
get
|
||||
{
|
||||
if (resourceMan is null)
|
||||
resourceMan = new global::System.Resources.ResourceManager("ChatTwo.Resources.HellionStrings", typeof(HellionStrings).Assembly);
|
||||
resourceMan = new global::System.Resources.ResourceManager("HellionChat.Resources.HellionStrings", typeof(HellionStrings).Assembly);
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,8 @@ internal class HellionStrings
|
||||
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));
|
||||
|
||||
@@ -259,8 +261,22 @@ internal class HellionStrings
|
||||
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));
|
||||
}
|
||||
+33
-3
@@ -19,7 +19,7 @@
|
||||
<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>
|
||||
@@ -211,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>
|
||||
@@ -406,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>
|
||||
@@ -556,7 +562,7 @@
|
||||
<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>
|
||||
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||
<value>ChatTwo Standard</value>
|
||||
<value>Klassik (Chat 2 Default)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||
<value>Hoher Kontrast</value>
|
||||
@@ -585,6 +591,12 @@
|
||||
<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>
|
||||
@@ -594,4 +606,22 @@
|
||||
<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>
|
||||
</root>
|
||||
@@ -19,7 +19,7 @@
|
||||
<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>
|
||||
@@ -211,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>
|
||||
@@ -406,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>
|
||||
@@ -556,7 +562,7 @@
|
||||
<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>
|
||||
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||
<value>ChatTwo Default</value>
|
||||
<value>Klassik (Chat 2 Default)</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||
<value>High-Contrast</value>
|
||||
@@ -585,6 +591,12 @@
|
||||
<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>
|
||||
@@ -594,4 +606,22 @@
|
||||
<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>
|
||||
</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>
|
||||
@@ -208,6 +208,12 @@
|
||||
<data name="Options_ChatColours_Import">
|
||||
<value>Import from game</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Tint channel selector with channel colour</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="Options_Tabs_Tab">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
@@ -1189,6 +1195,12 @@
|
||||
<data name="Options_HideInBattle_Description" xml:space="preserve">
|
||||
<value>Hide the chat during battles.</value>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Hide while New Game+ menu is open</value>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Hide the chat while the New Game+ menu is open. Closing the menu shows the chat again.</value>
|
||||
</data>
|
||||
<data name="Options_Emote_EmoteStats" xml:space="preserve">
|
||||
<value>Emote Stats</value>
|
||||
</data>
|
||||
+1
-1
@@ -1364,7 +1364,7 @@
|
||||
<value>启动</value>
|
||||
</data>
|
||||
<data name="Webinterface_Start_Success" xml:space="preserve">
|
||||
<value>网页界面已停止。</value>
|
||||
<value>网页界面已启动。</value>
|
||||
</data>
|
||||
<data name="Webinterface_Start_Failed" xml:space="preserve">
|
||||
<value>网页界面启动失败。检查 /xllog 获取更多信息。</value>
|
||||
@@ -2,7 +2,7 @@
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.Sheets;
|
||||
|
||||
namespace ChatTwo;
|
||||
namespace HellionChat;
|
||||
|
||||
public static class Sheets
|
||||
{
|
||||
@@ -37,7 +37,7 @@ public static class Sheets
|
||||
|
||||
public static IEnumerable<World> WorldsOnDatacenter(IPlayerCharacter character)
|
||||
{
|
||||
var dcRow = character.HomeWorld.Value.DataCenter.Value.Region.RowId;
|
||||
return WorldSheet.Where(world => world.IsPublic && world.DataCenter.Value.Region.RowId == dcRow);
|
||||
var dcRow = character.HomeWorld.Value.DataCenter.RowId;
|
||||
return WorldSheet.Where(world => world.IsPublic && world.DataCenter.RowId == dcRow);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
internal class AutoCompleteInfo
|
||||
{
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Hellion Chat — v0.6.0 input bar component for pop-out windows.
|
||||
//
|
||||
@@ -3,11 +3,11 @@ using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
@@ -22,7 +22,7 @@ using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Lumina.Extensions;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public sealed class ChatLogWindow : Window
|
||||
{
|
||||
@@ -66,6 +66,14 @@ public sealed class ChatLogWindow : Window
|
||||
public Vector2 LastWindowPos { get; private set; } = Vector2.Zero;
|
||||
public Vector2 LastWindowSize { get; private set; } = Vector2.Zero;
|
||||
|
||||
// Window position recovery: guards against off-screen positions after a
|
||||
// display layout change (monitor disconnected, resolution changed). On
|
||||
// the first draw after plugin load we run a one-shot bounds check to see
|
||||
// whether the stored position still overlaps any visible viewport area.
|
||||
// The manual reset button in the settings forces the position regardless.
|
||||
private bool DidOnLoadBoundsCheck;
|
||||
internal bool RequestPositionReset { get; set; }
|
||||
|
||||
public unsafe ImGuiViewport* LastViewport;
|
||||
private bool WasDocked;
|
||||
|
||||
@@ -270,9 +278,11 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
case "hide":
|
||||
CurrentHideState = HideState.User;
|
||||
Plugin.Log.Verbose("HideState: → User (chat hide command)");
|
||||
break;
|
||||
case "show":
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.Log.Verbose("HideState: → None (chat show command)");
|
||||
break;
|
||||
case "toggle":
|
||||
CurrentHideState = CurrentHideState switch
|
||||
@@ -282,6 +292,7 @@ public sealed class ChatLogWindow : Window
|
||||
HideState.None => HideState.User,
|
||||
_ => CurrentHideState,
|
||||
};
|
||||
Plugin.Log.Verbose($"HideState: → {CurrentHideState} (chat toggle command)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -347,6 +358,14 @@ public sealed class ChatLogWindow : Window
|
||||
if (Plugin.Config.PreviewPosition is PreviewPosition.Inside)
|
||||
height -= Plugin.InputPreview.PreviewHeight;
|
||||
|
||||
// Hellion Chat v0.6.1 — Header-Toolbar rendert auf Window-Ebene über
|
||||
// einem horizontalen Layout-Pfad und wird von GetContentRegionAvail
|
||||
// hier drin NICHT automatisch berücksichtigt, daher expliziter Abzug.
|
||||
// Banner dagegen rendert in DrawChatLog VOR diesem ganzen Block und
|
||||
// ImGui zieht seine Höhe automatisch von GetContentRegionAvail ab,
|
||||
// weil der Cursor schon weiter unten steht — kein eigener Abzug.
|
||||
height -= ImGui.GetFrameHeightWithSpacing();
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
@@ -390,30 +409,48 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
// if the chat has no hide state set, and the player has entered battle, we hide chat if they have configured it
|
||||
if (Plugin.Config.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.Battle;
|
||||
Plugin.Log.Verbose("HideState: None → Battle");
|
||||
}
|
||||
|
||||
// If the chat is hidden because of battle, we reset it here
|
||||
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.Log.Verbose("HideState: Battle → None");
|
||||
}
|
||||
|
||||
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
|
||||
if (Plugin.Config.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive))
|
||||
{
|
||||
if (Plugin.Functions.Chat.CheckHideFlags())
|
||||
{
|
||||
CurrentHideState = HideState.Cutscene;
|
||||
Plugin.Log.Verbose("HideState: None → 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)
|
||||
{
|
||||
Plugin.Log.Verbose($"HideState: {CurrentHideState} → None (cutscene/gpose ended)");
|
||||
CurrentHideState = HideState.None;
|
||||
}
|
||||
|
||||
// if the chat is hidden because of a cutscene and the chat has been activated, show chat
|
||||
if (CurrentHideState == HideState.Cutscene && Activate)
|
||||
{
|
||||
CurrentHideState = HideState.CutsceneOverride;
|
||||
Plugin.Log.Verbose("HideState: Cutscene → CutsceneOverride (user activate)");
|
||||
}
|
||||
|
||||
// if the user hid the chat and is now activating chat, reset the hide state
|
||||
if (CurrentHideState == HideState.User && Activate)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.Log.Verbose("HideState: User → None (activate)");
|
||||
}
|
||||
|
||||
if (CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Plugin.Config.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn))
|
||||
{
|
||||
@@ -534,6 +571,22 @@ public sealed class ChatLogWindow : Window
|
||||
LastWindowSize = currentSize;
|
||||
LastWindowPos = ImGui.GetWindowPos();
|
||||
|
||||
// Window position recovery. Manual reset takes precedence and snaps
|
||||
// the window to the safe default unconditionally; the one-shot
|
||||
// on-load check only fires when the persisted position has no
|
||||
// overlap with any visible viewport area.
|
||||
if (RequestPositionReset)
|
||||
{
|
||||
RequestPositionReset = false;
|
||||
DidOnLoadBoundsCheck = true;
|
||||
ApplySafeDefaultPosition("manual-reset");
|
||||
}
|
||||
else if (!DidOnLoadBoundsCheck)
|
||||
{
|
||||
DidOnLoadBoundsCheck = true;
|
||||
EnsureWindowOnScreen("on-load");
|
||||
}
|
||||
|
||||
if (resized)
|
||||
LastResize.Restart();
|
||||
|
||||
@@ -543,6 +596,12 @@ public sealed class ChatLogWindow : Window
|
||||
if (IsChatMode && Plugin.InputPreview.IsDrawable)
|
||||
Plugin.InputPreview.CalculatePreview();
|
||||
|
||||
// Hellion Chat v0.6.1 — render the one-time hint banner first so it
|
||||
// sits above the tab area / sidebar in full window width. Stash the
|
||||
// height for GetRemainingHeightForMessageLog so the message log
|
||||
// shrinks accordingly while the banner is visible.
|
||||
_v061HintBannerHeight = DrawV061HintBannerIfNeeded();
|
||||
|
||||
if (Plugin.Config.SidebarTabView)
|
||||
DrawTabSidebar();
|
||||
else
|
||||
@@ -562,9 +621,40 @@ public sealed class ChatLogWindow : Window
|
||||
DrawChannelName(activeTab);
|
||||
}
|
||||
|
||||
// v1.0.2 — compute inputColour up front so the channel selector button
|
||||
// can also tint with it (existing input-text push remains below).
|
||||
var inputType = activeTab.CurrentChannel.UseTempChannel ? activeTab.CurrentChannel.TempChannel.ToChatType() : activeTab.CurrentChannel.Channel.ToChatType();
|
||||
var isCommand = Chat.Trim().StartsWith('/');
|
||||
if (isCommand)
|
||||
{
|
||||
var command = Chat.Split(' ')[0];
|
||||
if (TextCommandChannels.TryGetValue(command, out var channel))
|
||||
inputType = channel;
|
||||
|
||||
if (!IsValidCommand(command))
|
||||
inputType = ChatType.Error;
|
||||
}
|
||||
|
||||
var inputColour = Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColor();
|
||||
|
||||
if (!isCommand && Plugin.ExtraChat.ChannelOverride is var (_, overrideColour))
|
||||
inputColour = overrideColour;
|
||||
|
||||
if (isCommand && Plugin.ExtraChat.ChannelCommandColours.TryGetValue(Chat.Split(' ')[0], out var ecColour))
|
||||
inputColour = ecColour;
|
||||
|
||||
var beforeIcon = ImGui.GetCursorPos();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab.Channel is null)
|
||||
ImGui.OpenPopup(ChatChannelPicker);
|
||||
|
||||
var tintSelector = Plugin.Config.ColorSelectedInputChannelButton && inputColour.HasValue;
|
||||
var selectorAbgr = tintSelector ? ColourUtil.RgbaToAbgr(inputColour!.Value) : 0u;
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, selectorAbgr, tintSelector))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, ColourUtil.AdjustBrightness(selectorAbgr, 1.15f), tintSelector))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonActive, ColourUtil.AdjustBrightness(selectorAbgr, 0.85f), tintSelector))
|
||||
{
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Comment) && activeTab.Channel is null)
|
||||
ImGui.OpenPopup(ChatChannelPicker);
|
||||
}
|
||||
|
||||
if (activeTab.Channel is not null && ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(Language.ChatLog_SwitcherDisabled);
|
||||
@@ -588,27 +678,7 @@ public sealed class ChatLogWindow : Window
|
||||
var buttonsRight = (showNovice ? 1 : 0) + (Plugin.Config.ShowHideButton ? 1 : 0);
|
||||
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth * (1 + buttonsRight);
|
||||
|
||||
var inputType = activeTab.CurrentChannel.UseTempChannel ? activeTab.CurrentChannel.TempChannel.ToChatType() : activeTab.CurrentChannel.Channel.ToChatType();
|
||||
var isCommand = Chat.Trim().StartsWith('/');
|
||||
if (isCommand)
|
||||
{
|
||||
var command = Chat.Split(' ')[0];
|
||||
if (TextCommandChannels.TryGetValue(command, out var channel))
|
||||
inputType = channel;
|
||||
|
||||
if (!IsValidCommand(command))
|
||||
inputType = ChatType.Error;
|
||||
}
|
||||
|
||||
var normalColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var inputColour = Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColor();
|
||||
|
||||
if (!isCommand && Plugin.ExtraChat.ChannelOverride is var (_, overrideColour))
|
||||
inputColour = overrideColour;
|
||||
|
||||
if (isCommand && Plugin.ExtraChat.ChannelCommandColours.TryGetValue(Chat.Split(' ')[0], out var ecColour))
|
||||
inputColour = ecColour;
|
||||
|
||||
var push = inputColour != null;
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, push ? ColourUtil.RgbaToAbgr(inputColour!.Value) : 0, push))
|
||||
{
|
||||
@@ -1295,6 +1365,7 @@ public sealed class ChatLogWindow : Window
|
||||
TabSwitched(tab, previousTab);
|
||||
|
||||
tab.Unread = 0;
|
||||
DrawChatHeaderToolbar(tab);
|
||||
DrawMessageLog(tab, PayloadHandler, GetRemainingHeightForMessageLog(), hasTabSwitched);
|
||||
}
|
||||
|
||||
@@ -1427,11 +1498,85 @@ public sealed class ChatLogWindow : Window
|
||||
}
|
||||
|
||||
if (currentTab > -1)
|
||||
{
|
||||
DrawChatHeaderToolbar(Plugin.Config.Tabs[currentTab]);
|
||||
DrawMessageLog(Plugin.Config.Tabs[currentTab], PayloadHandler, childHeight, hasTabSwitched);
|
||||
}
|
||||
|
||||
Plugin.WantedTab = null;
|
||||
}
|
||||
|
||||
// Hellion Chat v0.6.1 — visible pop-out trigger right above the message
|
||||
// log so users discover the feature without having to right-click the tab.
|
||||
// Renders only for the active tab in the main ChatLogWindow; pop-out
|
||||
// windows have their own render path and skip this toolbar.
|
||||
private void DrawChatHeaderToolbar(Tab tab)
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail().X;
|
||||
var iconWidth = ImGui.GetFrameHeight();
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + avail - iconWidth);
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.WindowRestore, tooltip: Language.ChatLog_Tabs_PopOut))
|
||||
{
|
||||
tab.PopOut = true;
|
||||
Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Hellion Chat v0.6.1 — One-Time-Hint-Banner introducing the chat header
|
||||
// pop-out toolbar button and the right-click pathway. Reuses the visual
|
||||
// pattern from Popout.cs DrawHintBannerIfNeeded so users see a familiar
|
||||
// dismiss-affordance. Returns the vertical space the banner consumed
|
||||
// (0 when not shown) so the message log can shrink accordingly.
|
||||
private float DrawV061HintBannerIfNeeded()
|
||||
{
|
||||
if (Plugin.Config.SeenPopOutHeaderHint)
|
||||
return 0f;
|
||||
|
||||
var hintText = Resources.HellionStrings.Hint_v061_PopOutHeader_Body;
|
||||
var ackLabel = Resources.HellionStrings.Hint_v061_PopOutHeader_Ack;
|
||||
var openLabel = Resources.HellionStrings.Hint_v061_PopOutHeader_OpenSettings;
|
||||
|
||||
var startY = ImGui.GetCursorPosY();
|
||||
|
||||
var bg = new System.Numerics.Vector4(0.16f, 0.20f, 0.28f, 1f);
|
||||
ImGui.PushStyleColor(ImGuiCol.ChildBg, bg);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
|
||||
|
||||
var dismiss = false;
|
||||
var openSettings = false;
|
||||
using (var child = ImRaii.Child("##v061-pop-out-header-hint", new System.Numerics.Vector2(0f, 84f), true))
|
||||
{
|
||||
if (child)
|
||||
{
|
||||
ImGui.TextWrapped(hintText);
|
||||
if (ImGui.Button(ackLabel))
|
||||
dismiss = true;
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(openLabel))
|
||||
{
|
||||
dismiss = true;
|
||||
openSettings = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.Spacing();
|
||||
|
||||
if (dismiss)
|
||||
{
|
||||
Plugin.Config.SeenPopOutHeaderHint = true;
|
||||
Plugin.SaveConfig();
|
||||
Plugin.Log.Debug("v0.6.1 pop-out header hint dismissed");
|
||||
if (openSettings)
|
||||
Plugin.SettingsWindow.Toggle();
|
||||
}
|
||||
|
||||
return ImGui.GetCursorPosY() - startY;
|
||||
}
|
||||
|
||||
private void DrawTabContextMenu(Tab tab, int i)
|
||||
{
|
||||
using var contextMenu = ImRaii.ContextPopupItem($"tab-context-menu-{i}");
|
||||
@@ -1491,6 +1636,13 @@ public sealed class ChatLogWindow : Window
|
||||
internal readonly List<bool> PopOutDocked = [];
|
||||
internal readonly HashSet<Guid> PopOutWindows = [];
|
||||
|
||||
// Hellion Chat v0.6.1 — height the v0.6.1 hint banner consumed in the
|
||||
// current frame, read by GetRemainingHeightForMessageLog so the message
|
||||
// log can shrink. Unconditionally reassigned at the top of DrawChatLog
|
||||
// (before any tab-area render) so the value is always in sync with the
|
||||
// current frame. Returns 0 once the banner is dismissed.
|
||||
private float _v061HintBannerHeight;
|
||||
|
||||
// v0.6.0 — live enumeration of all active Popout windows so the
|
||||
// KeybindManager can find a focused ChatInputBar to forward tab-cycle
|
||||
// keybinds to. Filter on IsOpen prevents touching closed-but-still-
|
||||
@@ -1939,4 +2091,47 @@ public sealed class ChatLogWindow : Window
|
||||
var hashCode = $"{Salt}{playerName}{worldId}".GetHashCode();
|
||||
return $"Player {hashCode:X8}";
|
||||
}
|
||||
|
||||
// Snap threshold in pixels: at least this much of the window must overlap
|
||||
// a visible viewport so the user can still grab the first tab header.
|
||||
// Below the threshold the window is considered off-screen.
|
||||
private const int OnScreenMinOverlapX = 100;
|
||||
private const int OnScreenMinOverlapY = 40;
|
||||
|
||||
// Default snap position relative to the primary viewport (top-left with a
|
||||
// safety margin from the game title bar).
|
||||
private static readonly Vector2 SafeDefaultOffset = new(50, 50);
|
||||
|
||||
private void EnsureWindowOnScreen(string source)
|
||||
{
|
||||
if (LastWindowSize.X < 1 || LastWindowSize.Y < 1)
|
||||
return;
|
||||
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
var visibleMin = viewport.WorkPos;
|
||||
var visibleMax = viewport.WorkPos + viewport.WorkSize;
|
||||
|
||||
var overlapMin = Vector2.Max(LastWindowPos, visibleMin);
|
||||
var overlapMax = Vector2.Min(LastWindowPos + LastWindowSize, visibleMax);
|
||||
var overlap = overlapMax - overlapMin;
|
||||
|
||||
if (overlap.X >= OnScreenMinOverlapX && overlap.Y >= OnScreenMinOverlapY)
|
||||
return;
|
||||
|
||||
ApplySafeDefaultPosition(source);
|
||||
}
|
||||
|
||||
private void ApplySafeDefaultPosition(string source)
|
||||
{
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
var safePos = viewport.WorkPos + SafeDefaultOffset;
|
||||
Position = safePos;
|
||||
Plugin.Log.Info(
|
||||
$"[Window-Recovery] {source}: snapping main window from {LastWindowPos} (size {LastWindowSize}) to {safePos}.");
|
||||
|
||||
// Pop-outs are intentionally non-persistent (cleared on plugin reload),
|
||||
// so an off-screen pop-out can never survive a session boundary. The
|
||||
// main window above is the only persistence target that needs an
|
||||
// explicit recovery path.
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public class CommandHelpWindow : Window {
|
||||
private ChatLogWindow LogWindow { get; }
|
||||
@@ -2,9 +2,9 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
@@ -18,7 +18,7 @@ using Lumina.Data.Files;
|
||||
using Lumina.Text.ReadOnly;
|
||||
using MoreLinq;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public class DbViewer : Window
|
||||
{
|
||||
@@ -391,6 +391,10 @@ public class DbViewer : Window
|
||||
await rangeMessageEnumerator.DisposeAsync();
|
||||
|
||||
var filteredHistory = Filter(messageHistory);
|
||||
// Materialize Count once — re-enumerating the IEnumerable on
|
||||
// every batch (twice per batch in the Notification update)
|
||||
// turned the export into an O(N²) hot loop on large histories.
|
||||
var totalCount = filteredHistory.Count;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
await using var stream = new StreamWriter(Path.Join(InputPath, $"Chat2_{DateTime.Now:yyyy_dd_M__HH_mm_ss}.txt"));
|
||||
@@ -416,8 +420,8 @@ public class DbViewer : Window
|
||||
}
|
||||
}, delayTicks: 5);
|
||||
|
||||
Notification.Progress = (float)batch / filteredHistory.Count;
|
||||
Notification.Content = $"Exported {batch} of {filteredHistory.Count} messages";
|
||||
Notification.Progress = (float)batch / totalCount;
|
||||
Notification.Content = $"Exported {batch} of {totalCount} messages";
|
||||
await stream.WriteAsync(sb.ToString());
|
||||
sb.Clear();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
@@ -7,9 +7,9 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Lumina.Text.ReadOnly;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public class DebuggerWindow : Window
|
||||
public class DebuggerWindow : Window, IDisposable
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private readonly ChatLogWindow ChatLogWindow;
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Privacy;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Privacy;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public sealed class FirstRunWizard : Window
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
/// <summary>
|
||||
/// ImGui style override for Hellion Chat. Industrial HUD palette with three
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
@@ -12,7 +12,7 @@ using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public partial class InputPreview : Window
|
||||
{
|
||||
@@ -4,7 +4,7 @@ using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
internal class Popout : Window
|
||||
{
|
||||
@@ -22,6 +22,11 @@ internal class Popout : Window
|
||||
public ChatInputBar? InputBar { get; private set; }
|
||||
public bool HasFocusedInputBar => InputBar?.IsFocused ?? false;
|
||||
|
||||
// Hellion Chat — v0.6.1 expose just the tab identifier (not the whole Tab
|
||||
// reference) so AutoTellTabsService.DropOldestTempTab can locate the
|
||||
// matching pop-out window when an LRU temp tab gets evicted.
|
||||
internal Guid TabIdentifier => Tab.Identifier;
|
||||
|
||||
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
|
||||
{
|
||||
ChatLogWindow = chatLogWindow;
|
||||
@@ -75,7 +80,10 @@ internal class Popout : Window
|
||||
if (!Tab.CanResize)
|
||||
Flags |= ImGuiWindowFlags.NoResize;
|
||||
|
||||
if (!ChatLogWindow.PopOutDocked[Idx])
|
||||
// Idx may point past the end if PopOutDocked was resized (e.g., a tab
|
||||
// dropped) between the AddPopOutsToDraw() snapshot and this frame.
|
||||
// Guard the read so we don't index into stale state.
|
||||
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count && !ChatLogWindow.PopOutDocked[Idx])
|
||||
{
|
||||
if (Tab.IndependentOpacity)
|
||||
{
|
||||
@@ -190,7 +198,8 @@ internal class Popout : Window
|
||||
|
||||
public override void PostDraw()
|
||||
{
|
||||
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
||||
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count)
|
||||
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
||||
|
||||
if (Plugin.Config is { OverrideStyle: true, ChosenStyle: not null })
|
||||
StyleModel.GetConfiguredStyles()?.FirstOrDefault(style => style.Name == Plugin.Config.ChosenStyle)?.Pop();
|
||||
@@ -220,30 +229,48 @@ internal class Popout : Window
|
||||
{
|
||||
// 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;
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → Battle");
|
||||
}
|
||||
|
||||
// If the chat is hidden because of battle, we reset it here
|
||||
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Battle → 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;
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → 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)
|
||||
{
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: {CurrentHideState} → None (cutscene/gpose ended)");
|
||||
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;
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Cutscene → CutsceneOverride (user activate)");
|
||||
}
|
||||
|
||||
// if the user hid the chat and is now activating chat, reset the hide state
|
||||
if (CurrentHideState == HideState.User && ChatLogWindow.Activate)
|
||||
{
|
||||
CurrentHideState = HideState.None;
|
||||
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: User → None (activate)");
|
||||
}
|
||||
|
||||
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
@@ -10,7 +10,7 @@ using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Utility;
|
||||
using DalamudPartyFinderPayload = Dalamud.Game.Text.SeStringHandling.Payloads.PartyFinderPayload;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public class SeStringDebugger : Window
|
||||
{
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui.SettingsTabs;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Ui.SettingsTabs;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
@@ -9,7 +9,7 @@ using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Appearance : ISettingsTab
|
||||
{
|
||||
@@ -236,6 +236,10 @@ internal sealed class Appearance : ISettingsTab
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(Language.Options_ColorSelectedInputChannelButton_Name, ref Mutable.ColorSelectedInputChannelButton);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
@@ -1,13 +1,13 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.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;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
// Chat-Tab — vier eigenständige Sektionen: Auto-Tell-Tabs, Behaviour,
|
||||
// Preview, Emotes. Der Emotes-Block ist 1:1 aus der Bestand-Datei
|
||||
@@ -73,6 +73,9 @@ internal sealed class Chat : ISettingsTab
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Compact_Name, ref Mutable.AutoTellTabsCompactDisplay);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Name, ref Mutable.AutoTellTabsOpenAsPopout);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_OpenAsPopout_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, ref Mutable.AutoTellTabsShowGreetedToggle);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description);
|
||||
|
||||
@@ -166,6 +169,13 @@ internal sealed class Chat : ISettingsTab
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
|
||||
|
||||
// Open the selector popup on left-click; SelectorPopup uses
|
||||
// ImRaii.ContextPopupItem internally which only opens on right-
|
||||
// click otherwise — without this OpenPopup the button looked
|
||||
// active but the popup never appeared on a normal click.
|
||||
if (ImGui.IsItemClicked())
|
||||
ImGui.OpenPopup("WordAddPopup");
|
||||
|
||||
if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions))
|
||||
{
|
||||
Mutable.BlockedEmotes.Add(newWord);
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
@@ -9,7 +9,7 @@ using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.Text;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Database : ISettingsTab
|
||||
{
|
||||
@@ -1,10 +1,10 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class General : ISettingsTab
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal interface ISettingsTab
|
||||
{
|
||||
@@ -1,12 +1,12 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.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;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
// Information-Tab vereint die früheren About- und Changelog-Tabs in
|
||||
// drei kollabierbaren Sektionen. Der About-Inhalt ist 1:1 aus About.cs
|
||||
@@ -1,15 +1,15 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Export;
|
||||
using ChatTwo.Privacy;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Export;
|
||||
using HellionChat.Privacy;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Privacy : ISettingsTab
|
||||
{
|
||||
@@ -455,11 +455,18 @@ internal sealed class Privacy : ISettingsTab
|
||||
|
||||
if (deleted > 0)
|
||||
{
|
||||
Plugin.Framework.Run(() =>
|
||||
// Bound the wait so a hung framework tick can't deadlock
|
||||
// the background retention worker. Five seconds is well
|
||||
// beyond a normal frame; if we time out we log and let
|
||||
// the next FilterAllTabsAsync call recover the state.
|
||||
if (!Plugin.Framework.Run(() =>
|
||||
{
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}).Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}).Wait();
|
||||
Plugin.Log.Warning("Retention sweep: framework refresh timed out after 5s.");
|
||||
}
|
||||
}
|
||||
|
||||
WrapperUtil.AddNotification(string.Format(HellionStrings.Retention_Success, deleted), NotificationType.Success);
|
||||
@@ -615,11 +622,17 @@ internal sealed class Privacy : ISettingsTab
|
||||
var deleted = Plugin.MessageManager.Store.CleanupRetainOnly(allowed);
|
||||
Plugin.Log.Information($"Privacy cleanup: deleted {deleted} messages");
|
||||
|
||||
Plugin.Framework.Run(() =>
|
||||
// Bound the wait so a hung framework tick can't deadlock
|
||||
// the background cleanup worker. See the matching comment in
|
||||
// the retention path above for rationale.
|
||||
if (!Plugin.Framework.Run(() =>
|
||||
{
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}).Wait(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
Plugin.MessageManager.ClearAllTabs();
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}).Wait();
|
||||
Plugin.Log.Warning("Privacy cleanup: framework refresh timed out after 5s.");
|
||||
}
|
||||
|
||||
WrapperUtil.AddNotification(string.Format(HellionStrings.Cleanup_Success, deleted), NotificationType.Success);
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Tabs : ISettingsTab
|
||||
{
|
||||
@@ -181,28 +181,39 @@ internal sealed class Tabs : ISettingsTab
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
|
||||
if (selectedWorld == -1)
|
||||
selectedWorld = 0;
|
||||
|
||||
using (var combo = ImRaii.Combo("###player-world", worlds[selectedWorld].Name.ToString()))
|
||||
// Guard against an empty worlds list — can happen briefly
|
||||
// when switching characters or if the datacenter sheet
|
||||
// has not yet populated. Without the guard the indexed
|
||||
// access into worlds[selectedWorld] would crash.
|
||||
if (worlds.Count == 0)
|
||||
{
|
||||
if (combo.Success)
|
||||
ImGui.TextDisabled("(no worlds available)");
|
||||
}
|
||||
else
|
||||
{
|
||||
var selectedWorld = worlds.FindIndex(world => world.RowId == tab.TellTarget.World);
|
||||
if (selectedWorld == -1)
|
||||
selectedWorld = 0;
|
||||
|
||||
using (var combo = ImRaii.Combo("###player-world", worlds[selectedWorld].Name.ToString()))
|
||||
{
|
||||
var lastDc = worlds.First().DataCenter.RowId;
|
||||
foreach (var (idx, world) in worlds.Index())
|
||||
if (combo.Success)
|
||||
{
|
||||
if (ImGui.Selectable(world.Name.ToString(), selectedWorld == idx))
|
||||
var lastDc = worlds.First().DataCenter.RowId;
|
||||
foreach (var (idx, world) in worlds.Index())
|
||||
{
|
||||
selectedWorld = idx;
|
||||
tab.TellTarget.World = worlds[selectedWorld].RowId;
|
||||
if (ImGui.Selectable(world.Name.ToString(), selectedWorld == idx))
|
||||
{
|
||||
selectedWorld = idx;
|
||||
tab.TellTarget.World = worlds[selectedWorld].RowId;
|
||||
}
|
||||
|
||||
if (lastDc == world.DataCenter.RowId)
|
||||
continue;
|
||||
|
||||
lastDc = world.DataCenter.RowId;
|
||||
ImGui.Separator();
|
||||
}
|
||||
|
||||
if (lastDc == world.DataCenter.RowId)
|
||||
continue;
|
||||
|
||||
lastDc = world.DataCenter.RowId;
|
||||
ImGui.Separator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Window : ISettingsTab
|
||||
{
|
||||
@@ -56,6 +56,9 @@ internal sealed class Window : ISettingsTab
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideInBattle_Name, ref Mutable.HideInBattle);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideInBattle_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideInNewGamePlusMenu_Name, ref Mutable.HideInNewGamePlusMenu);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideInNewGamePlusMenu_Description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +145,15 @@ internal sealed class Window : ISettingsTab
|
||||
|
||||
ImGui.Checkbox(Language.Options_SidebarTabView_Name, ref Mutable.SidebarTabView);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName));
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// Manual escape hatch for off-screen windows. The plugin already
|
||||
// runs an automatic bounds check once per session, but a button
|
||||
// is the user-friendly fallback after a display layout change.
|
||||
if (ImGui.Button(HellionStrings.Settings_Window_ResetPosition_Name))
|
||||
Plugin.ChatLogWindow.RequestPositionReset = true;
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_ResetPosition_Description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,19 @@ using Pidgin;
|
||||
using static Pidgin.Parser;
|
||||
using static Pidgin.Parser<char>;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
namespace HellionChat.Util;
|
||||
|
||||
internal static class AutoTranslate
|
||||
{
|
||||
private static readonly Dictionary<ClientLanguage, List<AutoTranslateEntry>> Entries = new();
|
||||
private static readonly HashSet<(uint, uint)> ValidEntries = [];
|
||||
|
||||
// Serializes all reads and writes against Entries / ValidEntries.
|
||||
// PreloadCache spawns a worker thread that fills both, while the main
|
||||
// thread reads them via Matching / ReplaceWithPayload / StartsWithCommand
|
||||
// — without this lock the HashSet/Dictionary access is undefined.
|
||||
private static readonly object EntriesLock = new();
|
||||
|
||||
private static Parser<char, (string name, Maybe<IEnumerable<ISelectorPart>> selector)> Parser()
|
||||
{
|
||||
var sheetName = Any
|
||||
@@ -68,9 +74,17 @@ internal static class AutoTranslate
|
||||
|
||||
private static List<AutoTranslateEntry> AllEntries()
|
||||
{
|
||||
if (Entries.TryGetValue(Plugin.DataManager.Language, out var entries))
|
||||
return entries;
|
||||
lock (EntriesLock)
|
||||
{
|
||||
if (Entries.TryGetValue(Plugin.DataManager.Language, out var entries))
|
||||
return entries;
|
||||
|
||||
return BuildEntriesLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<AutoTranslateEntry> BuildEntriesLocked()
|
||||
{
|
||||
var shouldAdd = ValidEntries.Count == 0;
|
||||
|
||||
var parser = Parser();
|
||||
@@ -229,7 +243,10 @@ internal static class AutoTranslate
|
||||
return;
|
||||
|
||||
// populate the list of valid entries
|
||||
if (ValidEntries.Count == 0)
|
||||
bool needBuild;
|
||||
lock (EntriesLock)
|
||||
needBuild = ValidEntries.Count == 0;
|
||||
if (needBuild)
|
||||
AllEntries();
|
||||
|
||||
var start = -1;
|
||||
@@ -244,7 +261,10 @@ internal static class AutoTranslate
|
||||
var parts = tag[4..^1].Split(',', 2);
|
||||
if (parts.Length == 2 && uint.TryParse(parts[0], out var group) && uint.TryParse(parts[1], out var key))
|
||||
{
|
||||
var payload = ValidEntries.Contains((group, key)) ? CreateFixedTranslation(group, key) : [];
|
||||
bool isValid;
|
||||
lock (EntriesLock)
|
||||
isValid = ValidEntries.Contains((group, key));
|
||||
var payload = isValid ? CreateFixedTranslation(group, key) : [];
|
||||
|
||||
var oldBytes = bytes.ToArray();
|
||||
var lengthDiff = payload.Length - (i - start);
|
||||
@@ -271,7 +291,10 @@ internal static class AutoTranslate
|
||||
return false;
|
||||
|
||||
// populate the list of valid entries
|
||||
if (ValidEntries.Count == 0)
|
||||
bool needBuild;
|
||||
lock (EntriesLock)
|
||||
needBuild = ValidEntries.Count == 0;
|
||||
if (needBuild)
|
||||
AllEntries();
|
||||
|
||||
for (var i = 0; i < search.Length; i++)
|
||||
@@ -289,7 +312,10 @@ internal static class AutoTranslate
|
||||
var parts = tag[4..^1].Split(',', 2);
|
||||
if (parts.Length == 2 && uint.TryParse(parts[0], out var group) && uint.TryParse(parts[1], out var key))
|
||||
{
|
||||
if (!ValidEntries.Contains((group, key)))
|
||||
bool isValid;
|
||||
lock (EntriesLock)
|
||||
isValid = ValidEntries.Contains((group, key));
|
||||
if (!isValid)
|
||||
return false;
|
||||
|
||||
var evaluated = Plugin.Evaluator.Evaluate(new ReadOnlySeString(CreateFixedTranslation(group, key))).ToString();
|
||||
@@ -1,4 +1,4 @@
|
||||
using ChatTwo.Code;
|
||||
using HellionChat.Code;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using System.Text;
|
||||
@@ -6,7 +6,7 @@ using Lumina.Text.Payloads;
|
||||
|
||||
using PayloadType = Dalamud.Game.Text.SeStringHandling.PayloadType;
|
||||
|
||||
namespace ChatTwo.Util;
|
||||
namespace HellionChat.Util;
|
||||
|
||||
internal static class ChunkUtil
|
||||
{
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user