Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15f83c8b0e | |||
| c7253bdf02 | |||
| cf10c566dd | |||
| acfe838bc6 | |||
| 9e1f559644 | |||
| 2c79a67dae | |||
| 1687271bfd | |||
| cb5457ba2e | |||
| a701f6c103 | |||
| 8cad8651d2 | |||
| 61b547606c | |||
| 059cfa6e28 | |||
| 71d84e4486 | |||
| 92301869ed | |||
| c3d06a9c94 | |||
| 911c870e24 | |||
| 8cda19d993 | |||
| 62621ba855 | |||
| 497c259031 | |||
| 9ad9d2acd2 | |||
| 1b63765caa | |||
| 61764459ed |
@@ -0,0 +1,13 @@
|
|||||||
|
# HellionChat is a hobby project and does not solicit funding.
|
||||||
|
#
|
||||||
|
# If you want to support the work that made HellionChat possible,
|
||||||
|
# please consider supporting the upstream Chat 2 maintainers:
|
||||||
|
#
|
||||||
|
# Infiziert90 (Infi): https://ko-fi.com/infiii
|
||||||
|
# Anna Clemens: https://ko-fi.com/lojewalo
|
||||||
|
#
|
||||||
|
# Both Ko-fi pages are also linked in the plugin's settings panel.
|
||||||
|
|
||||||
|
# No platforms enabled — keep this file present so GitHub recognises
|
||||||
|
# the project as having considered funding without showing a Sponsor
|
||||||
|
# button on the repository page.
|
||||||
@@ -16,7 +16,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: HellionChat version
|
label: HellionChat version
|
||||||
description: From Settings → Information → Version
|
description: From Settings → Information → Version
|
||||||
placeholder: "0.5.1"
|
placeholder: "0.5.4"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
Thanks for contributing to HellionChat. Please fill in the sections
|
||||||
|
below so the review goes quickly. Delete sections that genuinely do
|
||||||
|
not apply, but do not delete the whole template.
|
||||||
|
|
||||||
|
If this is a security fix, stop here and use a private security
|
||||||
|
advisory instead:
|
||||||
|
https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- One or two sentences. What does this PR change and why. -->
|
||||||
|
|
||||||
|
## Type of change
|
||||||
|
|
||||||
|
<!-- Tick all that apply. -->
|
||||||
|
|
||||||
|
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change that adds behaviour)
|
||||||
|
- [ ] Breaking change (config migration, removed feature, or behaviour
|
||||||
|
change that user-visible defaults rely on)
|
||||||
|
- [ ] Documentation only
|
||||||
|
- [ ] Translation update
|
||||||
|
- [ ] Build, CI or tooling change
|
||||||
|
- [ ] Upstream cherry-pick from Chat 2
|
||||||
|
|
||||||
|
## Linked issue
|
||||||
|
|
||||||
|
<!-- e.g. "Closes #42" or "Refs #42". For trivial typo fixes, "n/a". -->
|
||||||
|
|
||||||
|
## How I tested this
|
||||||
|
|
||||||
|
<!--
|
||||||
|
- Built locally with `dotnet build -c Release`
|
||||||
|
- Ran `dotnet test`
|
||||||
|
- Loaded the plugin in-game on Windows / Linux / macOS via XIVLauncher
|
||||||
|
- Specific scenarios I exercised in-game
|
||||||
|
-->
|
||||||
|
|
||||||
|
## User-visible changes
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Anything the end user will notice. New settings, changed defaults,
|
||||||
|
new commands, new translations, removed behaviour. If none, write
|
||||||
|
"none".
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Compatibility notes
|
||||||
|
|
||||||
|
<!--
|
||||||
|
- Does this require a configuration migration? If yes, which version
|
||||||
|
bump and is it covered by the existing migration tests?
|
||||||
|
- Does this change the schema in MessageStore?
|
||||||
|
- Does this change the repo.json or HellionChat.yaml manifest fields?
|
||||||
|
- Does this affect the upstream cherry-pick path? See UPSTREAM_SYNC.md.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] I have read [CONTRIBUTING.md](../CONTRIBUTING.md) and
|
||||||
|
[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md).
|
||||||
|
- [ ] My change matches the existing code style (`.editorconfig`).
|
||||||
|
- [ ] I added or updated tests where the existing test infrastructure
|
||||||
|
made that practical, or I have explained why tests are not
|
||||||
|
applicable.
|
||||||
|
- [ ] I updated the README, in-plugin strings or documentation if my
|
||||||
|
change is user-visible.
|
||||||
|
- [ ] I did not include any AI-generated code without disclosing it
|
||||||
|
in the PR description (see [AI_DISCLOSURE.md](../AI_DISCLOSURE.md)).
|
||||||
|
- [ ] I confirm my contribution is released under the
|
||||||
|
[EUPL-1.2](../LICENSE).
|
||||||
@@ -3,9 +3,14 @@ name: Release
|
|||||||
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
# Triggered when a vX.Y.Z tag is pushed. Builds the plugin against the
|
||||||
# current Dalamud staging branch, locates the latest.zip produced by
|
# current Dalamud staging branch, locates the latest.zip produced by
|
||||||
# DalamudPackager and attaches it to the matching GitHub Release.
|
# DalamudPackager and attaches it to the matching GitHub Release.
|
||||||
# Does not consume any user-controlled event payload, only the tag name
|
#
|
||||||
# (validated by the on.tags filter) and the steps output of the locate
|
# User-controlled inputs touched by this workflow:
|
||||||
# step (path string from Get-ChildItem on a controlled directory).
|
# - 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
|
||||||
|
# goatcorp / GitHub. Nothing from a webhook event payload (issue/PR
|
||||||
|
# titles, commit messages, etc.) flows into a run-step.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -53,9 +58,101 @@ jobs:
|
|||||||
Write-Host "Found: $($zip.FullName)"
|
Write-Host "Found: $($zip.FullName)"
|
||||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||||
|
|
||||||
|
# Build a release body from the matching changelog block in
|
||||||
|
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||||
|
# workflow if no block exists for the tagged version, which is the
|
||||||
|
# automated counterpart to the "yaml + repo.json + release body
|
||||||
|
# kept in sync" rule.
|
||||||
|
#
|
||||||
|
# GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
|
||||||
|
# tag value is treated as a PowerShell variable, not as inline shell
|
||||||
|
# text. The strict regex below rejects anything that is not a clean
|
||||||
|
# semver tag before it is used to build a string.
|
||||||
|
- name: Generate release body
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
$tag = $env:TAG_NAME
|
||||||
|
if ($tag -notmatch '^v\d+\.\d+\.\d+$') {
|
||||||
|
throw "Refusing to generate release body for non-semver tag: $tag"
|
||||||
|
}
|
||||||
|
$version = $tag.Substring(1)
|
||||||
|
|
||||||
|
$yamlPath = "ChatTwo/HellionChat.yaml"
|
||||||
|
$raw = Get-Content -Path $yamlPath -Raw
|
||||||
|
|
||||||
|
$marker = "changelog: |-"
|
||||||
|
$idx = $raw.IndexOf($marker)
|
||||||
|
if ($idx -lt 0) { throw "changelog block not found in $yamlPath" }
|
||||||
|
|
||||||
|
# changelog: is the last top-level key in the manifest, so
|
||||||
|
# everything after the marker is the literal block. Strip the
|
||||||
|
# 2-space yaml indent from each line.
|
||||||
|
$afterMarker = $raw.Substring($idx + $marker.Length)
|
||||||
|
$changelogBody = (($afterMarker -split "`r?`n") | ForEach-Object {
|
||||||
|
if ($_ -match '^ ') { $_.Substring(2) } else { $_ }
|
||||||
|
}) -join "`n"
|
||||||
|
|
||||||
|
$header = "**Hellion Chat $version"
|
||||||
|
$start = $changelogBody.IndexOf($header)
|
||||||
|
if ($start -lt 0) {
|
||||||
|
throw "No changelog entry for version $version found in $yamlPath. Update the changelog block before tagging a release."
|
||||||
|
}
|
||||||
|
|
||||||
|
$rest = $changelogBody.Substring($start)
|
||||||
|
$nextHdr = $rest.IndexOf("`n`n**Hellion Chat ", 1)
|
||||||
|
$trailer = $rest.IndexOf("`n`n---")
|
||||||
|
|
||||||
|
if ($nextHdr -ge 0 -and ($trailer -lt 0 -or $nextHdr -lt $trailer)) {
|
||||||
|
$currentBlock = $rest.Substring(0, $nextHdr).TrimEnd()
|
||||||
|
} elseif ($trailer -ge 0) {
|
||||||
|
$currentBlock = $rest.Substring(0, $trailer).TrimEnd()
|
||||||
|
} else {
|
||||||
|
$currentBlock = $rest.TrimEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
$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.
|
||||||
|
'@
|
||||||
|
|
||||||
|
$body = $currentBlock + "`n" + $footer
|
||||||
|
$body | Out-File -FilePath release-body.md -Encoding utf8 -NoNewline
|
||||||
|
|
||||||
|
Write-Host "Generated release body for $tag :"
|
||||||
|
Write-Host "----------------------------------------"
|
||||||
|
Write-Host $body
|
||||||
|
Write-Host "----------------------------------------"
|
||||||
|
|
||||||
- name: Attach to GitHub release
|
- name: Attach to GitHub release
|
||||||
uses: softprops/action-gh-release@v3
|
uses: softprops/action-gh-release@v3
|
||||||
with:
|
with:
|
||||||
files: ${{ steps.locate.outputs.path }}
|
files: ${{ steps.locate.outputs.path }}
|
||||||
|
body_path: release-body.md
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
generate_release_notes: false
|
generate_release_notes: false
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Code of conduct
|
||||||
|
|
||||||
|
HellionChat is a small hobby project. The contributor base is tiny and
|
||||||
|
the moderation overhead I can afford is equally small, so this document
|
||||||
|
is short and direct.
|
||||||
|
|
||||||
|
## What I expect from contributors
|
||||||
|
|
||||||
|
- Be respectful in issues, pull requests, discussions and any other
|
||||||
|
project space (Discord, email).
|
||||||
|
- Keep feedback focused on the code, the design or the documentation.
|
||||||
|
Critique the work, not the person.
|
||||||
|
- Assume good intent. People come from different backgrounds, time
|
||||||
|
zones and skill levels. A clarifying question is almost always a
|
||||||
|
better first move than an accusation.
|
||||||
|
- Stay on topic. This project is about a Dalamud chat plugin. Off-topic
|
||||||
|
arguments belong elsewhere.
|
||||||
|
- Respect that I maintain this in my spare time. Replies can take a
|
||||||
|
few days. Please do not escalate just because a thread is quiet.
|
||||||
|
|
||||||
|
## What is not welcome
|
||||||
|
|
||||||
|
- Personal attacks, slurs, doxxing, sustained disruption of threads.
|
||||||
|
- Unsolicited private contact after I have asked someone to stop.
|
||||||
|
- Sharing of private conversations without consent.
|
||||||
|
- Any content that would put other contributors or end users at risk.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This applies to every space the project owns or that I run on its
|
||||||
|
behalf: the GitHub repository, GitHub Discussions, project-related
|
||||||
|
Discord conversations and the maintainer email address listed in
|
||||||
|
`SECURITY.md`.
|
||||||
|
|
||||||
|
It also applies when someone is identifiably representing the project
|
||||||
|
in another space, for example posting as a HellionChat maintainer in
|
||||||
|
the Dalamud Discord.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
If something here is being broken, contact me directly. Do not open a
|
||||||
|
public issue.
|
||||||
|
|
||||||
|
- Email: `kontakt@hellion-media.de`
|
||||||
|
- Discord DM: `@j.j_kazama`
|
||||||
|
|
||||||
|
Reports stay private. I will acknowledge within a few weekdays
|
||||||
|
(European business hours) and tell you what I plan to do.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
I am the sole maintainer, so enforcement is a single-person process.
|
||||||
|
Depending on what happened and how the person responds, I will pick
|
||||||
|
the lightest measure that resolves the issue:
|
||||||
|
|
||||||
|
1. Private note asking the behaviour to stop.
|
||||||
|
2. Public correction in the affected thread.
|
||||||
|
3. Edit or removal of the offending content.
|
||||||
|
4. Temporary block from the repository or related spaces.
|
||||||
|
5. Permanent block.
|
||||||
|
|
||||||
|
Severe cases skip the lower steps. I will not negotiate over
|
||||||
|
harassment or threats.
|
||||||
|
|
||||||
|
## Acknowledgement
|
||||||
|
|
||||||
|
This document is intentionally short and project-specific rather than
|
||||||
|
a copy of a longer template. If you need a more formal reference, the
|
||||||
|
[Contributor Covenant](https://www.contributor-covenant.org/) is a
|
||||||
|
widely adopted starting point and the spirit of this document is
|
||||||
|
compatible with it.
|
||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
# Contributing to HellionChat
|
||||||
|
|
||||||
|
Thanks for taking a look. HellionChat is a small, opinionated fork of
|
||||||
|
[Chat 2](https://github.com/Infiziert90/ChatTwo) maintained by one
|
||||||
|
person in spare time. This document explains what I am looking for,
|
||||||
|
what I am not, and how to make a contribution land smoothly.
|
||||||
|
|
||||||
|
## Before you open anything
|
||||||
|
|
||||||
|
- Read the [README](README.md) so you understand the scope: 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
|
||||||
|
Chat 2 are selective and conscious; not everything that lands there
|
||||||
|
belongs here.
|
||||||
|
- Read [SECURITY.md](SECURITY.md). Anything security-sensitive goes
|
||||||
|
through a private advisory, never a public issue or PR.
|
||||||
|
- Read the [code of conduct](CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
|
## What I will accept
|
||||||
|
|
||||||
|
- Bug fixes for behaviour documented in the README, the in-plugin
|
||||||
|
settings or the changelog.
|
||||||
|
- Translation contributions for Hellion-specific strings via direct
|
||||||
|
pull requests against `ChatTwo/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).
|
||||||
|
- Performance fixes with a measurable before/after.
|
||||||
|
- New features that fit the privacy-first scope and do not duplicate
|
||||||
|
what an existing Dalamud plugin already does well.
|
||||||
|
|
||||||
|
## What I will probably decline
|
||||||
|
|
||||||
|
- Re-introducing the webinterface or any remote-access feature. It was
|
||||||
|
removed in v0.2.0 on purpose. See README "Was gegenüber Chat 2 fehlt".
|
||||||
|
- Features that bypass the privacy filter or weaken the default
|
||||||
|
retention behaviour without an explicit, documented opt-in.
|
||||||
|
- Sweeping refactors that touch large parts of the upstream codebase.
|
||||||
|
They make selective upstream cherry-picks much harder and the
|
||||||
|
maintenance cost outweighs the benefit for a one-person project.
|
||||||
|
- AI-generated code dropped in without disclosure or human review. See
|
||||||
|
[AI_DISCLOSURE.md](AI_DISCLOSURE.md) for how I handle AI assistance
|
||||||
|
on my side; I expect comparable transparency from contributors.
|
||||||
|
|
||||||
|
If you are unsure whether an idea fits, open a feature-request issue
|
||||||
|
first and ask before writing code. I would rather say "no" to a
|
||||||
|
proposal than to a finished pull request.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Open an issue (bug or feature request) using the templates under
|
||||||
|
`.github/ISSUE_TEMPLATE/`. Skip this step only for trivial typos.
|
||||||
|
2. Fork the repository and branch off `main`. Branch naming is
|
||||||
|
informal; something like `fix/auto-tell-history-empty` or
|
||||||
|
`feat/adblock-light-mode` is plenty.
|
||||||
|
3. Match the existing code style. The repository ships an
|
||||||
|
`.editorconfig` that VS Code and Rider pick up automatically.
|
||||||
|
4. Keep commits focused. Several small commits with clear messages are
|
||||||
|
easier to review than one 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
|
||||||
|
`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
|
||||||
|
you to summarise the change, the testing you did and any
|
||||||
|
compatibility notes.
|
||||||
|
|
||||||
|
## Build and test
|
||||||
|
|
||||||
|
The project targets `net10.0-windows` against Dalamud SDK 15. To build
|
||||||
|
locally you need:
|
||||||
|
|
||||||
|
- .NET 10 SDK
|
||||||
|
- A working Dalamud development environment with `DALAMUD_HOME` set
|
||||||
|
(XIVLauncher installed and launched once is the simplest path)
|
||||||
|
- VS Code with the C# Dev Kit, Rider, or Visual Studio
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet restore
|
||||||
|
dotnet build ChatTwo.sln -c Release
|
||||||
|
dotnet test ChatTwo.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).
|
||||||
|
|
||||||
|
For a smoke test in-game: build, copy the output into your Dalamud
|
||||||
|
`devPlugins/HellionChat/` directory and load it through `/xlplugins`.
|
||||||
|
|
||||||
|
## Continuous integration
|
||||||
|
|
||||||
|
Every push and every pull request runs:
|
||||||
|
|
||||||
|
- `build.yml` — `dotnet build` and `dotnet test`
|
||||||
|
- `codeql.yml` — CodeQL security analysis
|
||||||
|
|
||||||
|
A pull request will not be merged while either of these is failing.
|
||||||
|
CodeQL findings on changed code need to be addressed; pre-existing
|
||||||
|
findings on untouched code are tracked separately.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
By submitting a pull request you confirm that:
|
||||||
|
|
||||||
|
- Your contribution is your own work, or you have the right to
|
||||||
|
contribute it under the project licence.
|
||||||
|
- You agree that your contribution will be released under the
|
||||||
|
[EUPL-1.2](LICENSE), the same licence as the rest of the project.
|
||||||
|
|
||||||
|
There is no separate CLA.
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
Hellion-specific strings live in `ChatTwo/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
|
||||||
|
**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
|
||||||
|
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo) instead.
|
||||||
|
|
||||||
|
## A note on response times
|
||||||
|
|
||||||
|
I respond on weekdays during European business hours and I take
|
||||||
|
weekends and FFXIV patch days off. A pull request that sits for a few
|
||||||
|
days has not been ignored; I just have not gotten to it yet. Pinging
|
||||||
|
once after a week is fine; please do not ping daily.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
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
|
called out in the yaml changelog so users can see what it
|
||||||
derives from. -->
|
derives from. -->
|
||||||
<Version>0.5.4</Version>
|
<Version>0.6.0</Version>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
||||||
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class ConfigKeyBind
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class Configuration : IPluginConfiguration
|
public class Configuration : IPluginConfiguration
|
||||||
{
|
{
|
||||||
private const int LatestVersion = 10;
|
private const int LatestVersion = 11;
|
||||||
|
|
||||||
public int Version { get; set; } = LatestVersion;
|
public int Version { get; set; } = LatestVersion;
|
||||||
|
|
||||||
@@ -103,6 +103,18 @@ public class Configuration : IPluginConfiguration
|
|||||||
// want the auto tabs themselves without the extra UI affordance.
|
// want the auto tabs themselves without the extra UI affordance.
|
||||||
public bool AutoTellTabsShowGreetedToggle;
|
public bool AutoTellTabsShowGreetedToggle;
|
||||||
|
|
||||||
|
// Hellion Chat — One-Time-Hint-Banner that introduces the v0.6.0 pop-out
|
||||||
|
// input feature. Set to true once the user dismisses the banner from a
|
||||||
|
// pop-out window; never reset after that.
|
||||||
|
public bool SeenPopOutInputHint;
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 master switch for the pop-out input bar.
|
||||||
|
// Global on purpose: per-tab makes no sense for Auto-Tell-Tabs which
|
||||||
|
// are session-only and would force the user to re-enable it for every
|
||||||
|
// new conversation. Default OFF so existing users see no behavior
|
||||||
|
// change after the v10→v11 migration.
|
||||||
|
public bool PopOutInputEnabled;
|
||||||
|
|
||||||
public int GetRetentionDays(ChatType type)
|
public int GetRetentionDays(ChatType type)
|
||||||
{
|
{
|
||||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||||
@@ -296,6 +308,9 @@ public class Configuration : IPluginConfiguration
|
|||||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||||
|
|
||||||
|
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||||
|
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -414,13 +414,13 @@ internal unsafe class KeybindManager : IDisposable {
|
|||||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward))
|
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward))
|
||||||
{
|
{
|
||||||
Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false;
|
Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false;
|
||||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
DispatchTabDelta(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward))
|
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward))
|
||||||
{
|
{
|
||||||
Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false;
|
Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false;
|
||||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
DispatchTabDelta(-1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +465,24 @@ internal unsafe class KeybindManager : IDisposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v0.6.0 — central dispatch for ChatTabForward/Backward. If a pop-out
|
||||||
|
// window currently has its compact input focused, the keybind is
|
||||||
|
// forwarded into that pop-out's ChatInputBar so the user navigates
|
||||||
|
// tabs in the window they are typing in. Otherwise the main window
|
||||||
|
// handles it (= v0.5.x behavior).
|
||||||
|
private void DispatchTabDelta(int delta)
|
||||||
|
{
|
||||||
|
foreach (var popout in Plugin.ChatLogWindow.ActivePopouts)
|
||||||
|
{
|
||||||
|
if (popout.HasFocusedInputBar && popout.InputBar != null)
|
||||||
|
{
|
||||||
|
popout.InputBar.HandleKeybindForward(delta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Plugin.ChatLogWindow.ChangeTabDelta(delta);
|
||||||
|
}
|
||||||
|
|
||||||
private static Keybind GetKeybind(string id)
|
private static Keybind GetKeybind(string id)
|
||||||
{
|
{
|
||||||
var outData = new FFXIVClientStructs.FFXIV.Client.System.Input.Keybind();
|
var outData = new FFXIVClientStructs.FFXIV.Client.System.Input.Keybind();
|
||||||
|
|||||||
@@ -44,6 +44,45 @@ tags:
|
|||||||
- Replacement
|
- Replacement
|
||||||
- Privacy
|
- Privacy
|
||||||
changelog: |-
|
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**
|
**Hellion Chat 0.5.4 — WrapText hardening**
|
||||||
|
|
||||||
Replaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with
|
Replaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ChatTwo;
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 shared input history. Replaces the embedded
|
||||||
|
// ChatLogWindow.InputBacklog so that pop-out windows with their own
|
||||||
|
// ChatInputBar can navigate the same Up/Down history as the main window.
|
||||||
|
// Index semantics are kept identical to the v0.5.x InputBacklog:
|
||||||
|
// index 0 = oldest entry
|
||||||
|
// index Count - 1 = newest entry
|
||||||
|
// Push performs move-to-newest deduplication: existing entries are
|
||||||
|
// removed before the new one is appended at the end.
|
||||||
|
public static class InputHistoryService
|
||||||
|
{
|
||||||
|
private const int MaxSize = 30;
|
||||||
|
private static readonly List<string> _entries = new();
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> Entries => _entries;
|
||||||
|
|
||||||
|
public static int Count => _entries.Count;
|
||||||
|
|
||||||
|
public static void Push(string entry)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(entry))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var trimmed = entry.Trim();
|
||||||
|
|
||||||
|
// Move-to-newest: existing entries are removed before the append
|
||||||
|
// so the same line typed twice does not occupy two history slots.
|
||||||
|
for (var i = 0; i < _entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (_entries[i] == trimmed)
|
||||||
|
{
|
||||||
|
_entries.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries.Add(trimmed);
|
||||||
|
if (_entries.Count > MaxSize)
|
||||||
|
_entries.RemoveAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? GetByCursor(int cursor)
|
||||||
|
{
|
||||||
|
if (cursor < 0 || cursor >= _entries.Count)
|
||||||
|
return null;
|
||||||
|
return _entries[cursor];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -152,6 +152,21 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hellion Chat v10 → v11 — adds the global Configuration.PopOutInputEnabled
|
||||||
|
// master switch and SeenPopOutInputHint flag for the v0.6.0 pop-out
|
||||||
|
// input feature. Lightweight migration: defaults both fields,
|
||||||
|
// no user-facing notification because the change is opt-in only.
|
||||||
|
if (Config.Version < 11)
|
||||||
|
{
|
||||||
|
Config.PopOutInputEnabled = false;
|
||||||
|
Config.SeenPopOutInputHint = false;
|
||||||
|
Config.Version = 11;
|
||||||
|
SaveConfig();
|
||||||
|
Log.Information(
|
||||||
|
"Migrated config v10 → v11: PopOutInputEnabled added (global, default off), " +
|
||||||
|
"SeenPopOutInputHint added (default false)");
|
||||||
|
}
|
||||||
|
|
||||||
// Hellion default tab layout for first-run and v10-wipe.
|
// Hellion default tab layout for first-run and v10-wipe.
|
||||||
// General catches player chat plus active gameplay events; the
|
// General catches player chat plus active gameplay events; the
|
||||||
// System tab takes the technical noise so it does not bury real
|
// System tab takes the technical noise so it does not bury real
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.Util;
|
||||||
|
|
||||||
|
namespace ChatTwo.Resources;
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 built-in colour presets for the ChatColours
|
||||||
|
// settings section. Read-only static data; users apply a preset via the
|
||||||
|
// settings UI which overwrites Configuration.ChatColours immediately.
|
||||||
|
// Battle-channel types are intentionally NOT covered by the stylistic
|
||||||
|
// presets so that combat-log tuning the user has done stays intact.
|
||||||
|
public sealed record ChatColourPreset(
|
||||||
|
string DisplayName,
|
||||||
|
string LocalizationKey,
|
||||||
|
bool IsBrandPreset,
|
||||||
|
IReadOnlyDictionary<ChatType, uint> Colours);
|
||||||
|
|
||||||
|
public static class ChatColourPresets
|
||||||
|
{
|
||||||
|
public static IReadOnlyDictionary<string, ChatColourPreset> All { get; } = BuildAll();
|
||||||
|
|
||||||
|
private static Dictionary<string, ChatColourPreset> BuildAll()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, ChatColourPreset>
|
||||||
|
{
|
||||||
|
["Default"] = new(
|
||||||
|
DisplayName: "ChatTwo Default",
|
||||||
|
LocalizationKey: "ChatColourPresets_Default",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildDefault()),
|
||||||
|
["HighContrast"] = new(
|
||||||
|
DisplayName: "High-Contrast",
|
||||||
|
LocalizationKey: "ChatColourPresets_HighContrast",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildHighContrast()),
|
||||||
|
["Pastell"] = new(
|
||||||
|
DisplayName: "Pastell",
|
||||||
|
LocalizationKey: "ChatColourPresets_Pastell",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildPastell()),
|
||||||
|
["DarkModeTuned"] = new(
|
||||||
|
DisplayName: "Dark-Mode-Tuned",
|
||||||
|
LocalizationKey: "ChatColourPresets_DarkModeTuned",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildDarkModeTuned()),
|
||||||
|
["Hellion"] = new(
|
||||||
|
DisplayName: "Hellion",
|
||||||
|
LocalizationKey: "ChatColourPresets_Hellion",
|
||||||
|
IsBrandPreset: true,
|
||||||
|
Colours: BuildHellion()),
|
||||||
|
["NightBlue"] = new(
|
||||||
|
DisplayName: "Night Blue",
|
||||||
|
LocalizationKey: "ChatColourPresets_NightBlue",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildNightBlue()),
|
||||||
|
["IndigoViolet"] = new(
|
||||||
|
DisplayName: "Indigo Violet",
|
||||||
|
LocalizationKey: "ChatColourPresets_IndigoViolet",
|
||||||
|
IsBrandPreset: false,
|
||||||
|
Colours: BuildIndigoViolet()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Default preset spiegelt 1:1 die Werte aus ChatTypeExt.DefaultColor.
|
||||||
|
// Channels ohne Default-Wert (return null) werden ausgelassen — wer sie
|
||||||
|
// anwenden will, behält seine aktuelle Farbe.
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildDefault()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<ChatType, uint>();
|
||||||
|
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||||
|
{
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
var def = type.DefaultColor();
|
||||||
|
if (def.HasValue)
|
||||||
|
dict[type] = def.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildHighContrast()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(255, 255, 255),
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 192, 0),
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 96, 0),
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 128, 255),
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 128, 255),
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(128, 192, 255),
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 128, 64),
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(96, 192, 255),
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 64),
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128),
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 128),
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128),
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 128),
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(128, 255, 192),
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(128, 192, 255),
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 128, 255),
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 128, 192),
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 96, 96),
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 96),
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 96),
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 96),
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(96, 255, 160),
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(96, 160, 255),
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 96, 255),
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 96, 160),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildPastell()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(232, 232, 232),
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(245, 216, 155),
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(245, 176, 155),
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(224, 176, 224),
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(224, 176, 224),
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(176, 204, 224),
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(224, 192, 160),
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(168, 200, 224),
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(200, 224, 176),
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(224, 176, 176),
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(224, 200, 176),
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(224, 224, 176),
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(200, 224, 176),
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(176, 224, 200),
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(176, 200, 224),
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(200, 176, 224),
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(224, 176, 200),
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(224, 160, 160),
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(224, 192, 160),
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(224, 224, 160),
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(192, 224, 160),
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(160, 224, 192),
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(160, 192, 224),
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(192, 160, 224),
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(224, 160, 192),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildDarkModeTuned()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 240, 240),
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 208, 64),
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 128, 64),
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(255, 160, 255),
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(255, 160, 255),
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(160, 208, 255),
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 160, 96),
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(128, 200, 255),
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(192, 255, 96),
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 160, 160),
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 192, 160),
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 255, 160),
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(192, 255, 160),
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(160, 255, 192),
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(160, 192, 255),
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(192, 160, 255),
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(255, 160, 192),
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(255, 128, 128),
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(255, 160, 128),
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(255, 255, 128),
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(160, 255, 128),
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(128, 255, 160),
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(128, 160, 255),
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(160, 128, 255),
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(255, 128, 160),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hellion brand preset — Arctic Cyan + Ember Orange palette aus
|
||||||
|
// /mnt/ssd-fast/Projekte/hellion-media/hellion-media-website/BRANDING.md
|
||||||
|
// (Schema-Stand 2026-04-16). Channels sind über das ganze Brand-Spektrum
|
||||||
|
// verteilt damit jede Zeile auf einen Glance unterscheidbar ist:
|
||||||
|
// Cyan-Familie für Standard/Tell, Ember + Warning für laute Channels,
|
||||||
|
// Status-Farben (Success, Danger) für Linkshells. CrossLinkshells
|
||||||
|
// nutzen die dunkleren/sattersten Varianten derselben Hue-Familien.
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildHellion()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
// Standard / Tell — Cyan-Familie (Brand-Primary)
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light #4DD9E8
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan #00BED2
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark #0097A7
|
||||||
|
|
||||||
|
// Laute Channels — Ember/Warning
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning #F0AD4E
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember #F97316
|
||||||
|
|
||||||
|
// Gruppen-Channels — Success/Ember-dark/Cyan
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success #5CB85C
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark #E85D04
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(77, 217, 232),// Cyan-light
|
||||||
|
|
||||||
|
// Linkshells 1-8 — über das ganze Brand-Spektrum verteilt
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(251, 146, 60), // Ember-light #FB923C
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(240, 173, 78), // Warning
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(92, 184, 92), // Success
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(77, 217, 232), // Cyan-light
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(249, 115, 22), // Brand Ember
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(217, 83, 79), // Danger #D9534F
|
||||||
|
|
||||||
|
// CrossWorld-Linkshells 1-8 — dunklere/sattersere Varianten
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(232, 93, 4), // Ember-dark
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(200, 140, 50), // Warning-dark
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(60, 140, 60), // Success-dark
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(0, 190, 210), // Brand Cyan
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(0, 151, 167), // Cyan-dark
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(0, 110, 130), // Cyan-darker
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(220, 90, 30), // Ember-medium
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(170, 60, 60), // Danger-dark
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus preset — Night Blue, KAZAMA-Stimmungs-Theme aus
|
||||||
|
// /mnt/HDD-Data1/Obsidian/Vault/Systeme/KAZAMA/Theming/Night Blue + Indigo Violet Themes.md
|
||||||
|
// Klassisch, kühl, technisch — Marineblau-Tiefe ohne Lila-Anteil.
|
||||||
|
// Bewusst NICHT als Brand-Preset markiert (Vault-Boundary): die KAZAMA-Themes
|
||||||
|
// sind persönliche Stimmungs-Themes, nicht Teil des Hellion-Brand-Systems.
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildNightBlue()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
// Standard / Tell — Royal Blue Akzent-Familie
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(230, 237, 247), // text-primary
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(106, 176, 255),// akzent-hot
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
|
||||||
|
|
||||||
|
// Laute Channels — Warning/Danger Status-Töne
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74), // warning
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122), // danger
|
||||||
|
|
||||||
|
// Gruppen — Success/Akzent-Variations
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151), // success
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100), // warm-orange-light
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(74, 144, 226), // akzent-primary
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(140, 160, 191),// text-dim
|
||||||
|
|
||||||
|
// Linkshells 1-8 — über Spektrum verteilt
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 220, 130),
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(130, 220, 100),
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(61, 220, 151),
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(100, 200, 220),
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(106, 176, 255),
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(140, 160, 191),
|
||||||
|
|
||||||
|
// CrossWorld-Linkshells — gedämpfte Variants
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(200, 130, 50),
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(220, 110, 80),
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(200, 180, 60),
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(90, 180, 80),
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(30, 170, 110),
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(50, 130, 170),
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(50, 110, 180),
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(90, 100, 130),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus preset — Indigo Violet, KAZAMA-Stimmungs-Theme aus demselben
|
||||||
|
// Vault-Doc. Warm-mystisch, "Galaxy/Glitter/Nordlicht" — tiefes Indigo
|
||||||
|
// mit kräftigem Violet-Akzent. Persönlicher Favorit (siehe Vault).
|
||||||
|
// Auch nicht als Brand-Preset (siehe NightBlue-Note oben).
|
||||||
|
private static IReadOnlyDictionary<ChatType, uint> BuildIndigoViolet()
|
||||||
|
{
|
||||||
|
return new Dictionary<ChatType, uint>
|
||||||
|
{
|
||||||
|
// Standard / Tell — Royal Violet Akzent-Familie
|
||||||
|
[ChatType.Say] = ColourUtil.ComponentsToRgba(240, 230, 255), // text-primary (light lavender)
|
||||||
|
[ChatType.TellIncoming] = ColourUtil.ComponentsToRgba(176, 124, 255),// akzent-hot
|
||||||
|
[ChatType.TellOutgoing] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
|
||||||
|
|
||||||
|
// Laute Channels — geteilt mit Night Blue (Status-Farben)
|
||||||
|
[ChatType.Yell] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||||
|
[ChatType.Shout] = ColourUtil.ComponentsToRgba(255, 92, 122),
|
||||||
|
|
||||||
|
// Gruppen
|
||||||
|
[ChatType.Party] = ColourUtil.ComponentsToRgba(61, 220, 151),
|
||||||
|
[ChatType.Alliance] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||||
|
[ChatType.FreeCompany] = ColourUtil.ComponentsToRgba(139, 77, 222), // akzent-primary
|
||||||
|
[ChatType.NoviceNetwork] = ColourUtil.ComponentsToRgba(168, 144, 208),// text-dim
|
||||||
|
|
||||||
|
// Linkshells 1-8
|
||||||
|
[ChatType.Linkshell1] = ColourUtil.ComponentsToRgba(255, 184, 74),
|
||||||
|
[ChatType.Linkshell2] = ColourUtil.ComponentsToRgba(255, 144, 100),
|
||||||
|
[ChatType.Linkshell3] = ColourUtil.ComponentsToRgba(255, 220, 130),
|
||||||
|
[ChatType.Linkshell4] = ColourUtil.ComponentsToRgba(200, 124, 255),
|
||||||
|
[ChatType.Linkshell5] = ColourUtil.ComponentsToRgba(176, 124, 255),
|
||||||
|
[ChatType.Linkshell6] = ColourUtil.ComponentsToRgba(139, 77, 222),
|
||||||
|
[ChatType.Linkshell7] = ColourUtil.ComponentsToRgba(130, 90, 200),
|
||||||
|
[ChatType.Linkshell8] = ColourUtil.ComponentsToRgba(168, 144, 208),
|
||||||
|
|
||||||
|
// CrossWorld-Linkshells
|
||||||
|
[ChatType.CrossLinkshell1] = ColourUtil.ComponentsToRgba(200, 130, 50),
|
||||||
|
[ChatType.CrossLinkshell2] = ColourUtil.ComponentsToRgba(220, 110, 80),
|
||||||
|
[ChatType.CrossLinkshell3] = ColourUtil.ComponentsToRgba(200, 180, 60),
|
||||||
|
[ChatType.CrossLinkshell4] = ColourUtil.ComponentsToRgba(130, 80, 180),
|
||||||
|
[ChatType.CrossLinkshell5] = ColourUtil.ComponentsToRgba(100, 60, 160),
|
||||||
|
[ChatType.CrossLinkshell6] = ColourUtil.ComponentsToRgba(91, 42, 154),
|
||||||
|
[ChatType.CrossLinkshell7] = ColourUtil.ComponentsToRgba(80, 50, 130),
|
||||||
|
[ChatType.CrossLinkshell8] = ColourUtil.ComponentsToRgba(117, 96, 160),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -242,4 +242,25 @@ internal class HellionStrings
|
|||||||
internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner));
|
internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner));
|
||||||
internal static string Tabs_Presets_Linkshell => Get(nameof(Tabs_Presets_Linkshell));
|
internal static string Tabs_Presets_Linkshell => Get(nameof(Tabs_Presets_Linkshell));
|
||||||
internal static string Tabs_Presets_Linkshell_Hint => Get(nameof(Tabs_Presets_Linkshell_Hint));
|
internal static string Tabs_Presets_Linkshell_Hint => Get(nameof(Tabs_Presets_Linkshell_Hint));
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 chat colour presets (display labels)
|
||||||
|
internal static string ChatColourPresets_Default => Get(nameof(ChatColourPresets_Default));
|
||||||
|
internal static string ChatColourPresets_HighContrast => Get(nameof(ChatColourPresets_HighContrast));
|
||||||
|
internal static string ChatColourPresets_Pastell => Get(nameof(ChatColourPresets_Pastell));
|
||||||
|
internal static string ChatColourPresets_DarkModeTuned => Get(nameof(ChatColourPresets_DarkModeTuned));
|
||||||
|
internal static string ChatColourPresets_Hellion => Get(nameof(ChatColourPresets_Hellion));
|
||||||
|
internal static string ChatColourPresets_NightBlue => Get(nameof(ChatColourPresets_NightBlue));
|
||||||
|
internal static string ChatColourPresets_IndigoViolet => Get(nameof(ChatColourPresets_IndigoViolet));
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 chat colour presets section copy
|
||||||
|
internal static string Settings_Appearance_Colours_PresetsHint => Get(nameof(Settings_Appearance_Colours_PresetsHint));
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 pop-out input master switch
|
||||||
|
internal static string Settings_Window_PopOutInputEnabled_Name => Get(nameof(Settings_Window_PopOutInputEnabled_Name));
|
||||||
|
internal static string Settings_Window_PopOutInputEnabled_Description => Get(nameof(Settings_Window_PopOutInputEnabled_Description));
|
||||||
|
|
||||||
|
// Hellion Chat — 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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -555,4 +555,43 @@
|
|||||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||||
<value>Wenn du mehrere Linkshells benutzt, empfiehlt der Maintainer einen Tab pro Shell für eine sauberere Übersicht. Tab duplizieren und je Kopie die Kanalauswahl einschränken.</value>
|
<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>
|
||||||
|
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||||
|
<value>ChatTwo Standard</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||||
|
<value>Hoher Kontrast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_Pastell" xml:space="preserve">
|
||||||
|
<value>Pastell</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_DarkModeTuned" xml:space="preserve">
|
||||||
|
<value>Dunkelmodus-optimiert</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_Hellion" xml:space="preserve">
|
||||||
|
<value>Hellion</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_NightBlue" xml:space="preserve">
|
||||||
|
<value>Night Blue</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_IndigoViolet" xml:space="preserve">
|
||||||
|
<value>Indigo Violet</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Appearance_Colours_PresetsHint" xml:space="preserve">
|
||||||
|
<value>Tipp: Presets überschreiben deine aktuellen Channel-Farben sofort.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Window_PopOutInputEnabled_Name" xml:space="preserve">
|
||||||
|
<value>Eingabe in Pop-Outs aktivieren</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Window_PopOutInputEnabled_Description" xml:space="preserve">
|
||||||
|
<value>Master-Switch: erlaubt direktes Tippen und Absenden in jedem Pop-Out-Fenster (inkl. Auto-Tell-Tabs). Channel-Wechsel im Pop-Out wirkt global wie im Hauptfenster; Text-Buffer und History-Cursor sind pro Pop-Out unabhängig.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintText" xml:space="preserve">
|
||||||
|
<value>Neu in v0.6.0: Du kannst jetzt direkt im Pop-Out tippen. Master-Switch in den Fenster-Settings aktivieren.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintAck" xml:space="preserve">
|
||||||
|
<value>Verstanden</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintOpenSettings" xml:space="preserve">
|
||||||
|
<value>Fenster-Settings öffnen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -555,4 +555,43 @@
|
|||||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||||
<value>If you use multiple linkshells, the maintainer recommends one tab per shell for cleaner readability. Duplicate this tab and narrow the channel selection per copy.</value>
|
<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>
|
||||||
|
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||||
|
<value>ChatTwo Default</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_HighContrast" xml:space="preserve">
|
||||||
|
<value>High-Contrast</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_Pastell" xml:space="preserve">
|
||||||
|
<value>Pastell</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_DarkModeTuned" xml:space="preserve">
|
||||||
|
<value>Dark-Mode-Tuned</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_Hellion" xml:space="preserve">
|
||||||
|
<value>Hellion</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_NightBlue" xml:space="preserve">
|
||||||
|
<value>Night Blue</value>
|
||||||
|
</data>
|
||||||
|
<data name="ChatColourPresets_IndigoViolet" xml:space="preserve">
|
||||||
|
<value>Indigo Violet</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Appearance_Colours_PresetsHint" xml:space="preserve">
|
||||||
|
<value>Tip: presets overwrite your current channel colours immediately.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Window_PopOutInputEnabled_Name" xml:space="preserve">
|
||||||
|
<value>Enable input in pop-outs</value>
|
||||||
|
</data>
|
||||||
|
<data name="Settings_Window_PopOutInputEnabled_Description" xml:space="preserve">
|
||||||
|
<value>Master switch: lets you type and send messages directly inside every pop-out window (including auto-tell tabs). Channel changes inside a pop-out apply globally just like in the main window; the text buffer and history cursor stay independent per pop-out.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintText" xml:space="preserve">
|
||||||
|
<value>New in v0.6.0: you can type directly inside pop-out windows. Toggle the master switch in the window settings to enable it.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintAck" xml:space="preserve">
|
||||||
|
<value>Got it</value>
|
||||||
|
</data>
|
||||||
|
<data name="Popout_v060_HintOpenSettings" xml:space="preserve">
|
||||||
|
<value>Open window settings</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -0,0 +1,246 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using ChatTwo.Code;
|
||||||
|
using ChatTwo.Util;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
|
||||||
|
namespace ChatTwo.Ui;
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 input bar component for pop-out windows.
|
||||||
|
//
|
||||||
|
// Pragmatischer Refactor-Scope: Render() ist ein leerer Marker-Stub für
|
||||||
|
// das Hauptfenster — der bestehende Input-Layer in ChatLogWindow bleibt
|
||||||
|
// unangetastet, weil ein 400-Zeilen-Extract aus einem 1926-Zeilen-File
|
||||||
|
// das v0.6.0-Risiko unverhältnismäßig erhöhen würde. Pop-Outs nutzen
|
||||||
|
// ausschließlich RenderCompact(), das ist der ganze v0.6.0-Mehrwert.
|
||||||
|
// Sollte das Hauptfenster selber später eine Compact-Variante brauchen
|
||||||
|
// (oder das große Extract sich aus anderem Grund lohnen), kann Render()
|
||||||
|
// in einem späteren Cycle gefüllt werden.
|
||||||
|
public sealed class ChatInputBar
|
||||||
|
{
|
||||||
|
private readonly Plugin _plugin;
|
||||||
|
private readonly ChatLogWindow _host;
|
||||||
|
private readonly Func<Tab?> _activeTabAccessor;
|
||||||
|
private readonly InputState _state = new();
|
||||||
|
|
||||||
|
public ChatInputBar(Plugin plugin, ChatLogWindow host, Func<Tab?> activeTabAccessor)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
_host = host;
|
||||||
|
_activeTabAccessor = activeTabAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputState State => _state;
|
||||||
|
public bool IsFocused { get; private set; }
|
||||||
|
|
||||||
|
// Stub. v0.6.0 belässt den Hauptfenster-Input wie er ist.
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact rendering for pop-out windows.
|
||||||
|
//
|
||||||
|
// v0.6.0 Compact-Layout: Channel-Icon-Button links (Background-Farbe
|
||||||
|
// aus ChatColours), Text-Input rechts daneben. Auto-Translate-Picker
|
||||||
|
// ist bewusst NICHT im Compact-Mode (Spec-Abweichung Layout D → A).
|
||||||
|
// Rechtfertigung: das Hauptfenster-Auto-Complete-Popup ist nicht ohne
|
||||||
|
// grossen Refactor pro Window instanzierbar; typische Pop-Out-Use-Cases
|
||||||
|
// (FC-Greeter, Club-Hostess) brauchen Auto-Translate selten dort.
|
||||||
|
// Eigene Compact-Auto-Complete-Implementation kann ein späterer
|
||||||
|
// Cycle nachreichen wenn Tester-Feedback das verlangt.
|
||||||
|
//
|
||||||
|
// Channel-Switch wirkt via Plugin.Functions.Chat global (FFXIV-API).
|
||||||
|
// Pro Pop-Out unabhängig bleiben Text-Buffer und History-Cursor.
|
||||||
|
public void RenderCompact()
|
||||||
|
{
|
||||||
|
var tab = _activeTabAccessor();
|
||||||
|
if (tab == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawChannelIconButton(tab);
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawCompactInput(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCompactInput(Tab tab)
|
||||||
|
{
|
||||||
|
// Input takes the whole remaining width — no auto-translate button
|
||||||
|
// reserved on the right side in v0.6.0 (see RenderCompact comment).
|
||||||
|
var inputWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
if (inputWidth < 60f)
|
||||||
|
inputWidth = 60f;
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(inputWidth);
|
||||||
|
|
||||||
|
// CallbackHistory wires up Up/Down navigation against the shared
|
||||||
|
// InputHistoryService. Submit is detected the same way the main
|
||||||
|
// window does it: via IsItemDeactivated + Enter, NOT EnterReturnsTrue
|
||||||
|
// (matching v0.5.x ChatLogWindow.cs behavior).
|
||||||
|
const ImGuiInputTextFlags flags = ImGuiInputTextFlags.CallbackHistory;
|
||||||
|
ImGui.InputText($"##chat-compact-input-{tab.Identifier}", ref _state.Buffer, 500, flags, CompactCallback);
|
||||||
|
|
||||||
|
IsFocused = ImGui.IsItemActive();
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivated()
|
||||||
|
&& (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter)))
|
||||||
|
{
|
||||||
|
SubmitCompact(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubmitCompact(Tab tab)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_state.Buffer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var text = _state.Buffer;
|
||||||
|
_state.Buffer = string.Empty;
|
||||||
|
_state.HistoryCursor = -1;
|
||||||
|
_host.SendChatBoxFromExternal(tab, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// History-navigation callback for the compact input. Mirrors the main
|
||||||
|
// window's logic but operates on _state.HistoryCursor and the shared
|
||||||
|
// InputHistoryService. Index semantics match v0.5.x InputBacklog:
|
||||||
|
// 0 = oldest, Count-1 = newest.
|
||||||
|
private unsafe int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
||||||
|
{
|
||||||
|
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var prev = _state.HistoryCursor;
|
||||||
|
switch (data.EventKey)
|
||||||
|
{
|
||||||
|
case ImGuiKey.UpArrow:
|
||||||
|
switch (_state.HistoryCursor)
|
||||||
|
{
|
||||||
|
case -1:
|
||||||
|
var offset = 0;
|
||||||
|
if (!string.IsNullOrWhiteSpace(_state.Buffer))
|
||||||
|
{
|
||||||
|
InputHistoryService.Push(_state.Buffer);
|
||||||
|
offset = 1;
|
||||||
|
}
|
||||||
|
_state.HistoryCursor = InputHistoryService.Count - 1 - offset;
|
||||||
|
break;
|
||||||
|
case > 0:
|
||||||
|
_state.HistoryCursor--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ImGuiKey.DownArrow:
|
||||||
|
if (_state.HistoryCursor != -1)
|
||||||
|
if (++_state.HistoryCursor >= InputHistoryService.Count)
|
||||||
|
_state.HistoryCursor = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev == _state.HistoryCursor)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var historyStr = InputHistoryService.GetByCursor(_state.HistoryCursor) ?? string.Empty;
|
||||||
|
data.DeleteChars(0, data.BufTextLen);
|
||||||
|
data.InsertChars(0, historyStr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawChannelIconButton(Tab tab)
|
||||||
|
{
|
||||||
|
var inputType = tab.CurrentChannel.UseTempChannel
|
||||||
|
? tab.CurrentChannel.TempChannel.ToChatType()
|
||||||
|
: tab.CurrentChannel.Channel.ToChatType();
|
||||||
|
|
||||||
|
var rgba = Plugin.Config.ChatColours.TryGetValue(inputType, out var c)
|
||||||
|
? c
|
||||||
|
: (inputType.DefaultColor() ?? 0xFFFFFFFFu);
|
||||||
|
var v3 = ColourUtil.RgbaToVector3(rgba);
|
||||||
|
var bg = new Vector4(v3.X, v3.Y, v3.Z, 1f);
|
||||||
|
|
||||||
|
// Compute readable foreground — black on bright, white on dark
|
||||||
|
var luminance = 0.2126f * v3.X + 0.7152f * v3.Y + 0.0722f * v3.Z;
|
||||||
|
var fg = luminance > 0.55f ? new Vector4(0f, 0f, 0f, 1f) : new Vector4(1f, 1f, 1f, 1f);
|
||||||
|
|
||||||
|
const string popupId = "chat-channel-picker-compact";
|
||||||
|
const float buttonSize = 22f;
|
||||||
|
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Button, bg))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, bg))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ButtonActive, bg))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, fg))
|
||||||
|
{
|
||||||
|
// Single-letter glyph derived from the channel — quick visual cue
|
||||||
|
// until we have a proper icon font available in the compact bar.
|
||||||
|
var label = ChannelGlyph(inputType);
|
||||||
|
if (ImGui.Button($"{label}##chan-compact", new Vector2(buttonSize, buttonSize)) && tab.Channel is null)
|
||||||
|
ImGui.OpenPopup(popupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab.Channel is not null && ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(Resources.Language.ChatLog_SwitcherDisabled);
|
||||||
|
}
|
||||||
|
else if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(inputType.Name());
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var popup = ImRaii.Popup(popupId))
|
||||||
|
{
|
||||||
|
if (popup)
|
||||||
|
{
|
||||||
|
var channels = _host.GetValidChannels();
|
||||||
|
foreach (var (name, channel) in channels)
|
||||||
|
if (ImGui.Selectable(name))
|
||||||
|
_host.SetChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ChannelGlyph(ChatType type) => type switch
|
||||||
|
{
|
||||||
|
ChatType.Say => "S",
|
||||||
|
ChatType.Yell => "Y",
|
||||||
|
ChatType.Shout => "!",
|
||||||
|
ChatType.TellIncoming or ChatType.TellOutgoing => "T",
|
||||||
|
ChatType.Party or ChatType.CrossParty => "P",
|
||||||
|
ChatType.Alliance => "A",
|
||||||
|
ChatType.FreeCompany => "F",
|
||||||
|
ChatType.NoviceNetwork => "N",
|
||||||
|
ChatType.Linkshell1 => "1",
|
||||||
|
ChatType.Linkshell2 => "2",
|
||||||
|
ChatType.Linkshell3 => "3",
|
||||||
|
ChatType.Linkshell4 => "4",
|
||||||
|
ChatType.Linkshell5 => "5",
|
||||||
|
ChatType.Linkshell6 => "6",
|
||||||
|
ChatType.Linkshell7 => "7",
|
||||||
|
ChatType.Linkshell8 => "8",
|
||||||
|
ChatType.CrossLinkshell1 => "①",
|
||||||
|
ChatType.CrossLinkshell2 => "②",
|
||||||
|
ChatType.CrossLinkshell3 => "③",
|
||||||
|
ChatType.CrossLinkshell4 => "④",
|
||||||
|
ChatType.CrossLinkshell5 => "⑤",
|
||||||
|
ChatType.CrossLinkshell6 => "⑥",
|
||||||
|
ChatType.CrossLinkshell7 => "⑦",
|
||||||
|
ChatType.CrossLinkshell8 => "⑧",
|
||||||
|
_ => "?",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forwards a tab-cycle keybind delta to the host so all windows
|
||||||
|
// navigate the same active-tab pointer (single source of truth).
|
||||||
|
public void HandleKeybindForward(int delta)
|
||||||
|
{
|
||||||
|
_host.ChangeTabDelta(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-window input state. Each ChatInputBar instance owns one of these
|
||||||
|
// so pop-outs and the main window keep independent buffers and channels
|
||||||
|
// (State-Sync-Entscheidung A in the v0.6.0 spec).
|
||||||
|
public sealed class InputState
|
||||||
|
{
|
||||||
|
public string Buffer = string.Empty;
|
||||||
|
public InputChannel? Channel;
|
||||||
|
public int HistoryCursor = -1;
|
||||||
|
}
|
||||||
+30
-14
@@ -44,7 +44,10 @@ public sealed class ChatLogWindow : Window
|
|||||||
internal bool InputFocused { get; private set; }
|
internal bool InputFocused { get; private set; }
|
||||||
private int ActivatePos = -1;
|
private int ActivatePos = -1;
|
||||||
internal string Chat = string.Empty;
|
internal string Chat = string.Empty;
|
||||||
private readonly List<string> InputBacklog = [];
|
// Hellion Chat — v0.6.0 input history was extracted into
|
||||||
|
// InputHistoryService so pop-out windows with their own ChatInputBar
|
||||||
|
// share the same Up/Down history with the main window. The cursor
|
||||||
|
// stays window-local because each window navigates independently.
|
||||||
private int InputBacklogIdx = -1;
|
private int InputBacklogIdx = -1;
|
||||||
public bool TellSpecial;
|
public bool TellSpecial;
|
||||||
private readonly Stopwatch LastResize = new();
|
private readonly Stopwatch LastResize = new();
|
||||||
@@ -330,16 +333,10 @@ public sealed class ChatLogWindow : Window
|
|||||||
|
|
||||||
private void AddBacklog(string message)
|
private void AddBacklog(string message)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < InputBacklog.Count; i++)
|
// v0.6.0 — delegates to the shared InputHistoryService so pop-out
|
||||||
{
|
// ChatInputBar instances see the same history. Move-to-newest
|
||||||
if (InputBacklog[i] != message)
|
// deduplication lives inside the service.
|
||||||
continue;
|
InputHistoryService.Push(message);
|
||||||
|
|
||||||
InputBacklog.RemoveAt(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBacklog.Add(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetRemainingHeightForMessageLog()
|
private float GetRemainingHeightForMessageLog()
|
||||||
@@ -925,6 +922,18 @@ public sealed class ChatLogWindow : Window
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v0.6.0 — pop-out windows route submission through this wrapper.
|
||||||
|
// The main-window Chat buffer is briefly used as a vehicle for
|
||||||
|
// SendChatBox (which reads it directly) and restored afterwards so
|
||||||
|
// the main window does not visibly lose any half-typed input.
|
||||||
|
internal void SendChatBoxFromExternal(Tab tab, string text)
|
||||||
|
{
|
||||||
|
var saved = Chat;
|
||||||
|
Chat = text;
|
||||||
|
SendChatBox(tab);
|
||||||
|
Chat = saved;
|
||||||
|
}
|
||||||
|
|
||||||
internal void SendChatBox(Tab activeTab)
|
internal void SendChatBox(Tab activeTab)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(Chat))
|
if (!string.IsNullOrWhiteSpace(Chat))
|
||||||
@@ -1481,6 +1490,13 @@ public sealed class ChatLogWindow : Window
|
|||||||
|
|
||||||
internal readonly List<bool> PopOutDocked = [];
|
internal readonly List<bool> PopOutDocked = [];
|
||||||
internal readonly HashSet<Guid> PopOutWindows = [];
|
internal readonly HashSet<Guid> PopOutWindows = [];
|
||||||
|
|
||||||
|
// 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-
|
||||||
|
// registered popouts.
|
||||||
|
internal IEnumerable<Popout> ActivePopouts =>
|
||||||
|
Plugin.WindowSystem.Windows.OfType<Popout>().Where(p => p.IsOpen);
|
||||||
private void AddPopOutsToDraw()
|
private void AddPopOutsToDraw()
|
||||||
{
|
{
|
||||||
HandlerLender.ResetCounter();
|
HandlerLender.ResetCounter();
|
||||||
@@ -1757,7 +1773,7 @@ public sealed class ChatLogWindow : Window
|
|||||||
offset = 1;
|
offset = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputBacklogIdx = InputBacklog.Count - 1 - offset;
|
InputBacklogIdx = InputHistoryService.Count - 1 - offset;
|
||||||
break;
|
break;
|
||||||
case > 0:
|
case > 0:
|
||||||
InputBacklogIdx--;
|
InputBacklogIdx--;
|
||||||
@@ -1766,7 +1782,7 @@ public sealed class ChatLogWindow : Window
|
|||||||
break;
|
break;
|
||||||
case ImGuiKey.DownArrow:
|
case ImGuiKey.DownArrow:
|
||||||
if (InputBacklogIdx != -1)
|
if (InputBacklogIdx != -1)
|
||||||
if (++InputBacklogIdx >= InputBacklog.Count)
|
if (++InputBacklogIdx >= InputHistoryService.Count)
|
||||||
InputBacklogIdx = -1;
|
InputBacklogIdx = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1774,7 +1790,7 @@ public sealed class ChatLogWindow : Window
|
|||||||
if (prevPos == InputBacklogIdx)
|
if (prevPos == InputBacklogIdx)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var historyStr = InputBacklogIdx >= 0 ? InputBacklog[InputBacklogIdx] : string.Empty;
|
var historyStr = InputHistoryService.GetByCursor(InputBacklogIdx) ?? string.Empty;
|
||||||
data.DeleteChars(0, data.BufTextLen);
|
data.DeleteChars(0, data.BufTextLen);
|
||||||
data.InsertChars(0, historyStr);
|
data.InsertChars(0, historyStr);
|
||||||
|
|
||||||
|
|||||||
+89
-1
@@ -15,6 +15,13 @@ internal class Popout : Window
|
|||||||
private long FrameTime; // set every frame
|
private long FrameTime; // set every frame
|
||||||
private long LastActivityTime = Environment.TickCount64;
|
private long LastActivityTime = Environment.TickCount64;
|
||||||
|
|
||||||
|
// v0.6.0 — optional input bar inside the pop-out window. Lazy-allocated
|
||||||
|
// when the user enables Tab.PopOutInputEnabled and torn down when the
|
||||||
|
// toggle is turned off (independent text buffer is intentionally
|
||||||
|
// discarded — see v0.6.0 spec edge-case P1).
|
||||||
|
public ChatInputBar? InputBar { get; private set; }
|
||||||
|
public bool HasFocusedInputBar => InputBar?.IsFocused ?? false;
|
||||||
|
|
||||||
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
|
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
|
||||||
{
|
{
|
||||||
ChatLogWindow = chatLogWindow;
|
ChatLogWindow = chatLogWindow;
|
||||||
@@ -93,13 +100,94 @@ internal class Popout : Window
|
|||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v0.6.0 — one-time hint banner explaining the new pop-out input
|
||||||
|
// feature. Shown once per user; "Got it" or "Open settings"
|
||||||
|
// dismisses it and persists the flag.
|
||||||
|
var hintBannerHeight = DrawHintBannerIfNeeded();
|
||||||
|
|
||||||
|
// v0.6.0 — pop-out optional input bar. Reserve height first so the
|
||||||
|
// message log draws into the right region; only shown when the
|
||||||
|
// global master switch is on. Toggle-OFF resets InputBar so the
|
||||||
|
// next toggle-ON gives a fresh buffer (no stale text persists).
|
||||||
|
var inputEnabled = Plugin.Config.PopOutInputEnabled;
|
||||||
|
if (!inputEnabled && InputBar != null)
|
||||||
|
{
|
||||||
|
InputBar = null;
|
||||||
|
}
|
||||||
|
if (inputEnabled)
|
||||||
|
{
|
||||||
|
InputBar ??= new ChatInputBar(ChatLogWindow.Plugin, ChatLogWindow, () => Tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputBarHeight = inputEnabled
|
||||||
|
? ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y
|
||||||
|
: 0f;
|
||||||
|
|
||||||
var handler = ChatLogWindow.HandlerLender.Borrow();
|
var handler = ChatLogWindow.HandlerLender.Borrow();
|
||||||
ChatLogWindow.DrawMessageLog(Tab, handler, ImGui.GetContentRegionAvail().Y, false);
|
var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight - hintBannerHeight;
|
||||||
|
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false);
|
||||||
|
|
||||||
|
if (inputEnabled && InputBar != null)
|
||||||
|
{
|
||||||
|
ImGui.Separator();
|
||||||
|
InputBar.RenderCompact();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows))
|
if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows))
|
||||||
LastActivityTime = FrameTime;
|
LastActivityTime = FrameTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the vertical space the banner consumed (0 when not shown)
|
||||||
|
// so the message log can shrink accordingly.
|
||||||
|
private float DrawHintBannerIfNeeded()
|
||||||
|
{
|
||||||
|
if (Plugin.Config.SeenPopOutInputHint)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
var hintText = Resources.HellionStrings.Popout_v060_HintText;
|
||||||
|
var ackLabel = Resources.HellionStrings.Popout_v060_HintAck;
|
||||||
|
var openLabel = Resources.HellionStrings.Popout_v060_HintOpenSettings;
|
||||||
|
|
||||||
|
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("##v060-pop-out-hint", new System.Numerics.Vector2(0f, 64f), 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.SeenPopOutInputHint = true;
|
||||||
|
ChatLogWindow.Plugin.SaveConfig();
|
||||||
|
Plugin.Log.Debug("Pop-Out input hint dismissed");
|
||||||
|
if (openSettings)
|
||||||
|
ChatLogWindow.Plugin.SettingsWindow.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImGui.GetCursorPosY() - startY;
|
||||||
|
}
|
||||||
|
|
||||||
public override void PostDraw()
|
public override void PostDraw()
|
||||||
{
|
{
|
||||||
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
||||||
|
|||||||
@@ -230,6 +230,12 @@ internal sealed class Appearance : ISettingsTab
|
|||||||
|
|
||||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||||
{
|
{
|
||||||
|
DrawColourPresetButtons();
|
||||||
|
ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint);
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||||
{
|
{
|
||||||
foreach (var type in types)
|
foreach (var type in types)
|
||||||
@@ -263,6 +269,63 @@ internal sealed class Appearance : ISettingsTab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hellion Chat — v0.6.0 preset-buttons row above the per-channel colour
|
||||||
|
// editors. Apply is immediate and overwrites every channel covered by
|
||||||
|
// the preset; channels not in the preset keep their current colour.
|
||||||
|
private void DrawColourPresetButtons()
|
||||||
|
{
|
||||||
|
var first = true;
|
||||||
|
foreach (var (_, preset) in ChatColourPresets.All)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
if (preset.IsBrandPreset)
|
||||||
|
{
|
||||||
|
// Hellion-Brand visuell hervorheben — blau-violetter Frame-Akzent
|
||||||
|
var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200));
|
||||||
|
var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Border, new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f));
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button(GetPresetLabel(preset)))
|
||||||
|
{
|
||||||
|
ApplyPreset(preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset.IsBrandPreset)
|
||||||
|
{
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
ImGui.PopStyleColor(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Localized label for a preset; falls back to DisplayName if the i18n
|
||||||
|
// key is missing (defensive — the resource manager returns the key
|
||||||
|
// string itself when a lookup fails).
|
||||||
|
private static string GetPresetLabel(ChatColourPreset preset)
|
||||||
|
{
|
||||||
|
var localized = HellionStrings.ResourceManager.GetString(preset.LocalizationKey, HellionStrings.Culture);
|
||||||
|
return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPreset(ChatColourPreset preset)
|
||||||
|
{
|
||||||
|
foreach (var (channel, colour) in preset.Colours)
|
||||||
|
{
|
||||||
|
Mutable.ChatColours[channel] = colour;
|
||||||
|
}
|
||||||
|
Plugin.SaveConfig();
|
||||||
|
GlobalParametersCache.Refresh();
|
||||||
|
Plugin.Log.Debug($"Applied chat colour preset: {preset.DisplayName}");
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawTimestampsSection()
|
private void DrawTimestampsSection()
|
||||||
{
|
{
|
||||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Timestamps_Heading);
|
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Timestamps_Heading);
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ internal sealed class Window : ISettingsTab
|
|||||||
|
|
||||||
ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar);
|
ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar);
|
||||||
|
|
||||||
|
// v0.6.0 — global master switch for the pop-out input bar.
|
||||||
|
ImGui.Checkbox(HellionStrings.Settings_Window_PopOutInputEnabled_Name, ref Mutable.PopOutInputEnabled);
|
||||||
|
ImGuiUtil.HelpMarker(HellionStrings.Settings_Window_PopOutInputEnabled_Description);
|
||||||
|
|
||||||
ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton);
|
ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton);
|
||||||
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
|
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ to Chat 2 or to anything Infi or Anna would want flagged:
|
|||||||
|
|
||||||
- **GitHub Issues:** [JonKazama-Hellion/HellionChat/issues](https://github.com/JonKazama-Hellion/HellionChat/issues)
|
- **GitHub Issues:** [JonKazama-Hellion/HellionChat/issues](https://github.com/JonKazama-Hellion/HellionChat/issues)
|
||||||
- **Discord:** `@j.j_kazama`
|
- **Discord:** `@j.j_kazama`
|
||||||
- **Email (business):** maintainer@hellion-media.de
|
- **Email (business):** kontakt@hellion-media.de
|
||||||
|
|
||||||
I respond on weekdays during European business hours. For anything
|
I respond on weekdays during European business hours. For anything
|
||||||
urgent (security, attribution, takedown), email is the fastest path.
|
urgent (security, attribution, takedown), email is the fastest path.
|
||||||
|
|||||||
+259
@@ -0,0 +1,259 @@
|
|||||||
|
# Privacy notice
|
||||||
|
|
||||||
|
HellionChat is a Dalamud plugin for FINAL FANTASY XIV that focuses on
|
||||||
|
giving the user explicit control over what their chat client stores
|
||||||
|
locally. This document describes what the plugin does with your data,
|
||||||
|
what it does not do, and how you exercise the rights the GDPR gives
|
||||||
|
you over data you generate yourself.
|
||||||
|
|
||||||
|
This document is informational. The maintainer of HellionChat is
|
||||||
|
**not** a controller or processor of your data in the GDPR sense,
|
||||||
|
because no data ever leaves your machine on the maintainer's
|
||||||
|
infrastructure. Independently of that, the plugin is built so that
|
||||||
|
you can act on your own data the way the GDPR expects.
|
||||||
|
|
||||||
|
Last reviewed: 2026-05-03 (HellionChat v0.5.4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
- All chat data the plugin stores stays on your machine, in your
|
||||||
|
Dalamud `pluginConfigs/HellionChat/` directory.
|
||||||
|
- The plugin does not phone home. There is no telemetry, no analytics,
|
||||||
|
no crash reporter, no usage counter, no remote update check beyond
|
||||||
|
what Dalamud itself does.
|
||||||
|
- Two outbound network calls exist by design: the BetterTTV emote
|
||||||
|
service (for chat emotes) and the Square Enix Lodestone font CDN
|
||||||
|
(for the in-game symbol font). Both are documented in detail below
|
||||||
|
and both can be reasoned about per request.
|
||||||
|
- You can export every message the plugin has stored, in Markdown,
|
||||||
|
JSON or CSV, and you can wipe stored history per channel, per date
|
||||||
|
range, or globally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What the plugin stores locally
|
||||||
|
|
||||||
|
HellionChat keeps three kinds of state on your machine, all under
|
||||||
|
`%appdata%\XIVLauncher\pluginConfigs\HellionChat\` on Windows
|
||||||
|
(`~/.xlcore/pluginConfigs/HellionChat/` on Linux/macOS via XIVLauncher
|
||||||
|
Core):
|
||||||
|
|
||||||
|
1. **Configuration** (`HellionChat.json`)
|
||||||
|
Plugin settings, channel whitelist, retention values, layout state,
|
||||||
|
theme colours. Contains no chat content.
|
||||||
|
|
||||||
|
2. **Message database** (SQLite file in the same directory)
|
||||||
|
Chat messages from the channels on your whitelist, stored as
|
||||||
|
MessagePack-encoded blobs. Default whitelist out of the box covers
|
||||||
|
only your own conversations: tells, party, free company, linkshells,
|
||||||
|
cross-world linkshells, alliance, ExtraChat. Public chat, NPC
|
||||||
|
dialogue, system messages and battle logs are dropped on the
|
||||||
|
storage layer and never written to disk.
|
||||||
|
|
||||||
|
3. **Cached emote images** (`EmoteCacheV1/` directory)
|
||||||
|
Image files downloaded from BetterTTV when an emote appears in a
|
||||||
|
message you receive. See "Outbound network calls" below.
|
||||||
|
|
||||||
|
There is no shared state with the upstream Chat 2 plugin.
|
||||||
|
`pluginConfigs/HellionChat/` is independent from `pluginConfigs/ChatTwo/`.
|
||||||
|
|
||||||
|
### Retention defaults
|
||||||
|
|
||||||
|
- Tells: 365 days
|
||||||
|
- Your-conversation channels (party, FC, linkshells, cross-world LS,
|
||||||
|
alliance, ExtraChat): 90 days
|
||||||
|
- Global default for anything else: 30 days
|
||||||
|
|
||||||
|
**Retention is off by default.** The plugin does not delete anything
|
||||||
|
on its own until you explicitly turn the retention sweep on in the
|
||||||
|
settings. Until then, stored messages stay until you clear them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What the plugin does not store
|
||||||
|
|
||||||
|
- Public chat (`/say`, `/yell`, `/shout`), NPC dialogue, system
|
||||||
|
messages and battle logs. These are filtered before they reach the
|
||||||
|
storage layer.
|
||||||
|
- Anything from channels you remove from the whitelist. The privacy
|
||||||
|
filter runs on the way in, not on the way out.
|
||||||
|
- Login credentials, character IDs, account IDs. The plugin uses
|
||||||
|
whatever Dalamud already exposes about the local character to
|
||||||
|
attribute messages; nothing of that is sent anywhere or persisted
|
||||||
|
beyond the message itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outbound network calls
|
||||||
|
|
||||||
|
HellionChat makes two kinds of automatic outbound network requests.
|
||||||
|
Both are inherited from upstream Chat 2 and both are documented here
|
||||||
|
because "DSGVO-by-design" means you should know what your client does
|
||||||
|
on your behalf.
|
||||||
|
|
||||||
|
### 1. BetterTTV emote service (`api.betterttv.net`, `cdn.betterttv.net`)
|
||||||
|
|
||||||
|
- **What it does:** When a chat message arrives that references a
|
||||||
|
BetterTTV emote, the plugin asks the BetterTTV API for the emote
|
||||||
|
metadata and downloads the image from the BetterTTV CDN to display
|
||||||
|
it inline.
|
||||||
|
- **What is sent:** A standard HTTPS GET request. Your IP address
|
||||||
|
reaches BetterTTV (unavoidable for any HTTPS request); the request
|
||||||
|
itself contains no identifying user data, no character name, no
|
||||||
|
message text. Only the emote ID being looked up is in the URL path.
|
||||||
|
- **When it triggers:** Only when an incoming message contains an
|
||||||
|
emote token that is on the BetterTTV emote list.
|
||||||
|
- **Cached:** Yes, in `emoteCache/`. A given emote is downloaded once
|
||||||
|
per machine and reused.
|
||||||
|
- **How to opt out:** Turn off the **Show emotes** option in
|
||||||
|
Settings → Chat. With it disabled, the emote cache does not load
|
||||||
|
and no requests to BetterTTV are made for the rest of the session.
|
||||||
|
- **BetterTTV's privacy policy:** <https://betterttv.com/privacy>
|
||||||
|
|
||||||
|
Source: `ChatTwo/EmoteCache.cs`.
|
||||||
|
|
||||||
|
### 2. Square Enix Lodestone font (`img.finalfantasyxiv.com`)
|
||||||
|
|
||||||
|
- **What it does:** Downloads the `FFXIV_Lodestone_SSF.ttf` font file
|
||||||
|
from the official Square Enix Lodestone CDN once during font setup,
|
||||||
|
so the plugin can render in-game special symbols (job icons, item
|
||||||
|
glyphs, etc.) inside ImGui.
|
||||||
|
- **What is sent:** A single HTTPS GET request to the public Square
|
||||||
|
Enix font URL. Your IP address reaches Square Enix (unavoidable);
|
||||||
|
no character data, no plugin identifier, no message content.
|
||||||
|
- **When it triggers:** Once per font initialisation, not per session
|
||||||
|
if the file is already cached locally.
|
||||||
|
- **Cached:** Yes, by Dalamud's font subsystem.
|
||||||
|
- **How to opt out:** This call is part of the font pipeline inherited
|
||||||
|
from upstream Chat 2 and not toggleable from the settings UI today.
|
||||||
|
If a user-facing opt-out for this would be useful for you, please
|
||||||
|
open a feature-request issue.
|
||||||
|
|
||||||
|
Source: `ChatTwo/FontManager.cs`.
|
||||||
|
|
||||||
|
### Links you click yourself (no automatic traffic)
|
||||||
|
|
||||||
|
The settings panel contains a few buttons that open external pages in
|
||||||
|
your browser when you click them: the upstream Chat 2 GitHub repo,
|
||||||
|
the upstream maintainers' Ko-fi pages, the HellionChat issue tracker
|
||||||
|
and `hellion-media.de`. Nothing happens until you click. They are
|
||||||
|
documented here for completeness, not because they generate background
|
||||||
|
traffic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What the plugin does not do
|
||||||
|
|
||||||
|
- **No telemetry.** Source verified: no calls to AppInsights, Sentry,
|
||||||
|
PostHog, Plausible, Google Analytics, Microsoft Clarity or any
|
||||||
|
comparable service exist in the codebase, nor in the direct
|
||||||
|
dependencies the plugin pulls in. See `THIRD_PARTY_NOTICES.md`.
|
||||||
|
- **No crash reporting.** Crashes go to Dalamud's local `xllog`,
|
||||||
|
not to a remote endpoint controlled by HellionChat.
|
||||||
|
- **No usage counters.** The plugin does not count installs, sessions,
|
||||||
|
feature usage, channel activity or anything else for the maintainer.
|
||||||
|
- **No phone-home update check.** Updates are delivered through
|
||||||
|
Dalamud's plugin installer, which polls the custom-repo
|
||||||
|
`repo.json` on GitHub. That is GitHub's traffic and falls under
|
||||||
|
GitHub's privacy policy; the plugin code does no separate update
|
||||||
|
check.
|
||||||
|
- **No background sync.** Messages stay on your machine. There is no
|
||||||
|
cloud backup, no sharing feature, no remote viewer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Your data, your rights
|
||||||
|
|
||||||
|
The GDPR gives you specific rights over data about you. Because
|
||||||
|
HellionChat stores everything locally, those rights translate
|
||||||
|
directly into plugin features:
|
||||||
|
|
||||||
|
### Right to access (Art. 15)
|
||||||
|
|
||||||
|
Use the export feature in the plugin settings. You can export to
|
||||||
|
**Markdown**, **JSON** or **CSV**, filtered by channel, date range
|
||||||
|
or sender substring. The export goes through a Dalamud file dialog
|
||||||
|
and writes wherever you point it, on your machine.
|
||||||
|
|
||||||
|
### Right to erasure (Art. 17)
|
||||||
|
|
||||||
|
Two options:
|
||||||
|
|
||||||
|
1. **Targeted deletion** — the "retroactive cleanup" feature lets you
|
||||||
|
apply your current whitelist to the existing database. It shows a
|
||||||
|
preview of what will be removed before you confirm with
|
||||||
|
Ctrl+Shift, runs in the background, and calls `VACUUM` afterwards
|
||||||
|
to actually shrink the file.
|
||||||
|
2. **Full deletion** — close the game and delete the
|
||||||
|
`pluginConfigs/HellionChat/` directory. Next plugin start will
|
||||||
|
produce a fresh, empty configuration.
|
||||||
|
|
||||||
|
### Right to portability (Art. 20)
|
||||||
|
|
||||||
|
The JSON and CSV exports are open formats. The Markdown export is
|
||||||
|
human-readable and machine-parseable. Nothing is locked into a
|
||||||
|
proprietary container.
|
||||||
|
|
||||||
|
### Right to object / restrict processing (Art. 21, 18)
|
||||||
|
|
||||||
|
Adjust the channel whitelist or set retention to a low value. Both
|
||||||
|
take effect immediately on new messages; existing data needs the
|
||||||
|
retroactive cleanup to apply retroactively, by design.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Third parties involved
|
||||||
|
|
||||||
|
| Party | Why they appear | What reaches them | Their privacy policy |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| BetterTTV (NightDev LLC) | Optional emote rendering | HTTPS request for an emote ID; your IP | <https://betterttv.com/privacy> |
|
||||||
|
| Square Enix | Lodestone font download (once) | HTTPS request for the font file; your IP | <https://www.square-enix.com/privacy> |
|
||||||
|
| GitHub (Microsoft) | Plugin distribution via custom repo, issue tracker | Whatever GitHub sees from any HTTPS request to a public repo | <https://docs.github.com/site-policy/privacy-policies/github-general-privacy-statement> |
|
||||||
|
| Dalamud / XIVLauncher (goatcorp) | Plugin loader, font subsystem, repo polling | Whatever Dalamud reports for itself; out of HellionChat's scope | <https://github.com/goatcorp/Dalamud> |
|
||||||
|
|
||||||
|
Square Enix and GitHub are unavoidable for anyone playing FFXIV
|
||||||
|
through Dalamud at all. BetterTTV is the only third party HellionChat
|
||||||
|
introduces on top of the baseline that is not also part of using FFXIV
|
||||||
|
or Dalamud, and BetterTTV is opt-out via settings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies that touch the network
|
||||||
|
|
||||||
|
For a full dependency inventory see `THIRD_PARTY_NOTICES.md`. Of the
|
||||||
|
direct dependencies the plugin pulls in:
|
||||||
|
|
||||||
|
- `MessagePack` — local serialisation, no network.
|
||||||
|
- `Microsoft.Data.Sqlite` — local SQLite access, no network.
|
||||||
|
- `morelinq` — LINQ helpers, no network.
|
||||||
|
- `Pidgin` — parser combinators, no network.
|
||||||
|
- `SixLabors.ImageSharp` — image decoding (used for the BetterTTV
|
||||||
|
emote pipeline), no network on its own.
|
||||||
|
|
||||||
|
The two network calls listed under "Outbound network calls" are
|
||||||
|
written directly in HellionChat's own source, not delegated to a
|
||||||
|
dependency.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes to this notice
|
||||||
|
|
||||||
|
If a future release changes what HellionChat stores, sends or caches,
|
||||||
|
this document will be updated and the change called out in the
|
||||||
|
changelog block of that release. The "Last reviewed" date at the top
|
||||||
|
tracks the version this document is accurate for.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
For privacy-related questions specific to HellionChat:
|
||||||
|
|
||||||
|
- Email: `kontakt@hellion-media.de`
|
||||||
|
- Discord DM: `@j.j_kazama`
|
||||||
|
|
||||||
|
Security-relevant findings (e.g. the plugin storing or sending
|
||||||
|
something this document says it does not) go through the private
|
||||||
|
advisory in `SECURITY.md`, not a public issue.
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
[](https://dotnet.microsoft.com/)
|
[](https://dotnet.microsoft.com/)
|
||||||
[](https://www.finalfantasyxiv.com/)
|
[](https://www.finalfantasyxiv.com/)
|
||||||
|
|
||||||
**Version 0.5.1** — DSGVO-bewusste Erweiterung von [Chat 2](https://github.com/Infiziert90/ChatTwo) für FINAL FANTASY XIV / Dalamud.
|
**Version 0.5.4** — DSGVO-bewusste Erweiterung von [Chat 2](https://github.com/Infiziert90/ChatTwo) für FINAL FANTASY XIV / Dalamud.
|
||||||
|
|
||||||
Hellion Chat baut auf Chat 2 auf und ergänzt es um Datenschutz- und Daten-Handling-Kontrollen, die mit den Datenschutz-Regeln in der EU, den USA und Japan im Einklang sind. Alle Chat-2-Funktionen, Befehle und Tastenkürzel funktionieren unverändert. Eigenständiger Plugin-Slot, eigene Konfiguration, eigene Datenbank.
|
Hellion Chat baut auf Chat 2 auf und ergänzt es um Datenschutz- und Daten-Handling-Kontrollen, die mit den Datenschutz-Regeln in der EU, den USA und Japan im Einklang sind. Alle Chat-2-Funktionen, Befehle und Tastenkürzel funktionieren unverändert. Eigenständiger Plugin-Slot, eigene Konfiguration, eigene Datenbank.
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ Hellion Chat baut auf [Chat 2](https://github.com/Infiziert90/ChatTwo) von **Inf
|
|||||||
| Build | Dalamud.NET.Sdk 15.0.0, DalamudPackager 15.0.0 |
|
| Build | Dalamud.NET.Sdk 15.0.0, DalamudPackager 15.0.0 |
|
||||||
| UI | Dear ImGui (Dalamud-Bindings) |
|
| UI | Dear ImGui (Dalamud-Bindings) |
|
||||||
| Datenbank | SQLite (Microsoft.Data.Sqlite, MessagePack-Storage) |
|
| Datenbank | SQLite (Microsoft.Data.Sqlite, MessagePack-Storage) |
|
||||||
| Lokalisierung | ResX (HellionStrings.resx, .de.resx) + Crowdin-Sync |
|
| Lokalisierung | ResX (HellionStrings.resx, .de.resx; PR-basiert) |
|
||||||
| Schriftart | Exo 2 (SIL Open Font License 1.1, gebündelt) |
|
| Schriftart | Exo 2 (SIL Open Font License 1.1, gebündelt) |
|
||||||
| Toolchain | dotnet 10 SDK, VS Code mit C# Dev Kit |
|
| Toolchain | dotnet 10 SDK, VS Code mit C# Dev Kit |
|
||||||
| Deployment | GitHub Releases + Custom-Repo (`repo.json`) |
|
| Deployment | GitHub Releases + Custom-Repo (`repo.json`) |
|
||||||
@@ -44,6 +44,7 @@ Hellion Chat baut auf [Chat 2](https://github.com/Infiziert90/ChatTwo) von **Inf
|
|||||||
- **Aufbewahrungsdauer pro Kanal** mit täglicher Background-Bereinigung. Tells 365 Tage, eigene Konversations-Kanäle 90 Tage, globaler Default 30 Tage. Standard ist AUS, das Plugin löscht ohne ausdrückliche Zustimmung nichts.
|
- **Aufbewahrungsdauer pro Kanal** mit täglicher Background-Bereinigung. Tells 365 Tage, eigene Konversations-Kanäle 90 Tage, globaler Default 30 Tage. Standard ist AUS, das Plugin löscht ohne ausdrückliche Zustimmung nichts.
|
||||||
- **Retroaktive Säuberung** mit Vorschau und Strg+Umschalt-Bestätigung. Wendet die aktuelle Whitelist auf eine bestehende Datenbank an, läuft im Hintergrund, ruft danach VACUUM auf.
|
- **Retroaktive Säuberung** mit Vorschau und Strg+Umschalt-Bestätigung. Wendet die aktuelle Whitelist auf eine bestehende Datenbank an, läuft im Hintergrund, ruft danach VACUUM auf.
|
||||||
- **Export** nach Markdown, JSON oder CSV via Dalamud-Datei-Dialog (DSGVO Art. 15 Auskunftsrecht). Filter nach Kanal, Datums-Bereich oder Sender-Substring.
|
- **Export** nach Markdown, JSON oder CSV via Dalamud-Datei-Dialog (DSGVO Art. 15 Auskunftsrecht). Filter nach Kanal, Datums-Bereich oder Sender-Substring.
|
||||||
|
- **Vollständige Datenschutz-Übersicht** in [`PRIVACY.md`](PRIVACY.md): was gespeichert wird, welche zwei Outbound-Calls existieren (BetterTTV opt-out, Square-Enix-Lodestone-Font), explizite Telemetry-None-Zusage und das Mapping der DSGVO-Rechte (Art. 15/17/18/20/21) auf konkrete Plugin-Funktionen.
|
||||||
|
|
||||||
### Onboarding
|
### Onboarding
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ ChatTwo/
|
|||||||
|
|
||||||
- **Code-Namespace bleibt `ChatTwo.*`** — Cherry-Picks von Upstream-Bugfixes bleiben damit konfliktarm.
|
- **Code-Namespace bleibt `ChatTwo.*`** — Cherry-Picks von Upstream-Bugfixes bleiben damit konfliktarm.
|
||||||
- **AssemblyName ist `HellionChat`** — eigener Slot in `pluginConfigs/`, eigene Datei-Manifest, kein Shared State mit Chat 2.
|
- **AssemblyName ist `HellionChat`** — eigener Slot in `pluginConfigs/`, eigene Datei-Manifest, kein Shared State mit Chat 2.
|
||||||
- **Hellion-eigene Strings nur in `HellionStrings.*.resx`** — die Upstream-`Language.*.resx` bleiben unverändert für sauberen Crowdin-Sync.
|
- **Hellion-eigene Strings nur in `HellionStrings.*.resx`** — die Upstream-`Language.*.resx` bleiben unverändert, damit Cherry-Picks aus Chat 2 (inklusive deren Übersetzungs-Updates aus dem Upstream-Crowdin) konfliktarm bleiben.
|
||||||
- **Kein Direkt-Eingriff in `Plugin.Interface.UiBuilder.FontAtlas`** außerhalb von `FontManager` — Font-Fallback und Hellion-Font laufen zentral.
|
- **Kein Direkt-Eingriff in `Plugin.Interface.UiBuilder.FontAtlas`** außerhalb von `FontManager` — Font-Fallback und Hellion-Font laufen zentral.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -181,7 +182,7 @@ Spiel starten, Hellion Chat aktivieren, Verlauf ist zurück.
|
|||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
Updates erscheinen automatisch in der Plugin-Liste, sobald ein neuer `v0.1.x`-Tag mit GitHub-Release publiziert ist. Keine Neu-Installation nötig.
|
Updates erscheinen automatisch in der Plugin-Liste, sobald ein neuer `v0.X.Y`-Tag mit GitHub-Release publiziert ist. Keine Neu-Installation nötig.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -225,7 +226,7 @@ git log --oneline HEAD..upstream/main # Welche Commits gibt es?
|
|||||||
git cherry-pick -x <commit> # Selektiv übernehmen
|
git cherry-pick -x <commit> # Selektiv übernehmen
|
||||||
```
|
```
|
||||||
|
|
||||||
Konflikte in Upstream-Sprach-Ressourcen (`Language.<lang>.resx`) kommen häufig vor weil Crowdin sie regelmäßig anfasst. Pragmatisch mit `git checkout --theirs` auflösen, da wir sie selbst nicht editieren.
|
Konflikte in Upstream-Sprach-Ressourcen (`Language.<lang>.resx`) kommen häufig vor, weil Upstream-Übersetzungen (über das Chat-2-Crowdin-Projekt, nicht unseres) regelmäßig nachkommen. Pragmatisch mit `git checkout --theirs` auflösen, da wir sie selbst nicht editieren.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -243,7 +244,7 @@ Konflikte in Upstream-Sprach-Ressourcen (`Language.<lang>.resx`) kommen häufig
|
|||||||
|
|
||||||
## Projektstatus
|
## Projektstatus
|
||||||
|
|
||||||
**Version 0.3.1** | Stand: Mai 2026
|
**Version 0.5.4** | Stand: 2026-05-03
|
||||||
|
|
||||||
Alle Bootstrap-Phasen abgeschlossen:
|
Alle Bootstrap-Phasen abgeschlossen:
|
||||||
|
|
||||||
@@ -298,4 +299,20 @@ Siehe [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md) für die Pair-Level-Disclosure.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Projekt-Dokumente
|
||||||
|
|
||||||
|
| Dokument | Inhalt |
|
||||||
|
| --- | --- |
|
||||||
|
| [`PRIVACY.md`](PRIVACY.md) | Datenschutz-Übersicht: lokale Speicherung, Outbound-Calls, Telemetry-Status, DSGVO-Rechte und ihre Plugin-Entsprechungen. |
|
||||||
|
| [`SECURITY.md`](SECURITY.md) | Vulnerability-Reporting via Private Advisory, Scope und Disclosure-Fenster. |
|
||||||
|
| [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) | NuGet-Dependencies mit Lizenzen, Bundled Assets, Network-Status pro Komponente. |
|
||||||
|
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Was ich akzeptiere bzw. ablehne, Workflow, Build-Anleitung, EUPL-1.2-Bestätigung. |
|
||||||
|
| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Verhaltens-Erwartungen und Reporting-Pfad. |
|
||||||
|
| [`SUPPORT.md`](SUPPORT.md) | Wegweiser für Bugs, Security, Privacy, Quick-Questions. |
|
||||||
|
| [`UPSTREAM_SYNC.md`](UPSTREAM_SYNC.md) | Cherry-Pick-Policy gegenüber Chat 2. |
|
||||||
|
| [`NOTICE.md`](NOTICE.md) | Attribution an Upstream-Maintainer und Komponenten-Credits. |
|
||||||
|
| [`AI_DISCLOSURE.md`](AI_DISCLOSURE.md) | Offenlegung der KI-Unterstützung im Entwicklungsprozess. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Hellion Online Media** | Bad Harzburg | [hellion-media.de](https://hellion-media.de)
|
**Hellion Online Media** | Bad Harzburg | [hellion-media.de](https://hellion-media.de)
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ through GitHub's Security Advisories. This routes the report directly to
|
|||||||
me and keeps the conversation off the public timeline.
|
me and keeps the conversation off the public timeline.
|
||||||
|
|
||||||
**Alternative:**
|
**Alternative:**
|
||||||
- Email: `maintainer@hellion-media.de`
|
- Email: `kontakt@hellion-media.de`
|
||||||
- Discord: `@j.j_kazama`
|
- Discord: `@j.j_kazama`
|
||||||
|
|
||||||
I respond on weekdays during European business hours. For urgent
|
I respond on weekdays during European business hours. For urgent
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
# Support
|
||||||
|
|
||||||
|
HellionChat is a small hobby project maintained by one person. There
|
||||||
|
are a few different paths depending on what you need; please pick the
|
||||||
|
one that matches.
|
||||||
|
|
||||||
|
## Bugs and feature requests
|
||||||
|
|
||||||
|
GitHub issues, using the templates:
|
||||||
|
|
||||||
|
- [Bug report](https://github.com/JonKazama-Hellion/HellionChat/issues/new?template=bug_report.yml)
|
||||||
|
- [Feature request](https://github.com/JonKazama-Hellion/HellionChat/issues/new?template=feature_request.yml)
|
||||||
|
|
||||||
|
Please search [existing issues](https://github.com/JonKazama-Hellion/HellionChat/issues?q=is%3Aissue)
|
||||||
|
first. Duplicates get closed and pointed at the original.
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
Do **not** open a public issue for security-relevant findings. Use
|
||||||
|
the private advisory route described in [SECURITY.md](SECURITY.md):
|
||||||
|
|
||||||
|
- [Private vulnerability advisory](https://github.com/JonKazama-Hellion/HellionChat/security/advisories/new)
|
||||||
|
- Email `kontakt@hellion-media.de`
|
||||||
|
|
||||||
|
## Privacy questions
|
||||||
|
|
||||||
|
Specific questions about what HellionChat does or does not store and
|
||||||
|
send are covered in [PRIVACY.md](PRIVACY.md). For follow-ups beyond
|
||||||
|
that document:
|
||||||
|
|
||||||
|
- Email `kontakt@hellion-media.de`
|
||||||
|
|
||||||
|
## Quick questions and casual feedback
|
||||||
|
|
||||||
|
Discord DM `@j.j_kazama`. Bug reports still go through the issue
|
||||||
|
tracker so they can be tracked, but a quick "is this a bug or am I
|
||||||
|
holding it wrong" message is fine.
|
||||||
|
|
||||||
|
## Upstream Chat 2 issues
|
||||||
|
|
||||||
|
If the issue exists in upstream Chat 2 too, please report it at
|
||||||
|
[Infiziert90/ChatTwo](https://github.com/Infiziert90/ChatTwo/issues).
|
||||||
|
That keeps the original maintainers in the loop and helps everyone
|
||||||
|
who uses Chat 2 directly.
|
||||||
|
|
||||||
|
## Response times
|
||||||
|
|
||||||
|
Weekdays during European business hours. Weekends and FFXIV patch
|
||||||
|
days, replies will be slower. A few days of silence on a non-urgent
|
||||||
|
issue is normal; pinging once after a week is fine.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Third-party notices
|
||||||
|
|
||||||
|
HellionChat ships and depends on a number of third-party components.
|
||||||
|
This document lists them, their licences and which of them touch the
|
||||||
|
network. It is the inventory referenced by `PRIVACY.md`.
|
||||||
|
|
||||||
|
Last reviewed: 2026-05-03 (HellionChat v0.5.4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Direct NuGet dependencies
|
||||||
|
|
||||||
|
Pinned in `ChatTwo/ChatTwo.csproj`. Versions reflect the v0.5.4 build.
|
||||||
|
|
||||||
|
| Package | Version | Licence | Network | Purpose |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| [MessagePack](https://github.com/MessagePack-CSharp/MessagePack-CSharp) | 3.1.4 | MIT | no | Binary serialisation for the SQLite message store. |
|
||||||
|
| [Microsoft.Data.Sqlite](https://learn.microsoft.com/dotnet/standard/data/sqlite/) | 10.0.7 | MIT | no | Local SQLite access for the message database. |
|
||||||
|
| [morelinq](https://github.com/morelinq/MoreLINQ) | 4.4.0 | Apache-2.0 | no | LINQ helper extensions. |
|
||||||
|
| [Pidgin](https://github.com/benjamin-hodgson/Pidgin) | 3.3.0 | MIT | no | Parser combinator library used for chat-input parsing. |
|
||||||
|
| [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) | 3.1.12 | [Six Labors Split License 1.0](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) (OSI-approved; free for open-source / non-commercial use, commercial licence required for closed-source commercial use) | no | Image decoding for cached emotes. |
|
||||||
|
|
||||||
|
Six Labors note: HellionChat is an EUPL-1.2-licensed open-source
|
||||||
|
project distributed at no cost. Use of ImageSharp 3.x under the
|
||||||
|
Six Labors Split License 1.0 is permitted on that basis. Anyone
|
||||||
|
forking HellionChat for closed-source or commercial redistribution
|
||||||
|
should review the
|
||||||
|
[Six Labors licence terms](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE)
|
||||||
|
and obtain a commercial licence if required.
|
||||||
|
|
||||||
|
## SDK and tooling
|
||||||
|
|
||||||
|
| Component | Licence | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [Dalamud.NET.Sdk](https://github.com/goatcorp/Dalamud) 15.0.0 | AGPL-3.0 (Dalamud) / SDK terms per goatcorp | Plugin SDK; pulls in DalamudPackager 15.0.0. |
|
||||||
|
| [.NET 10 SDK](https://dotnet.microsoft.com/) | MIT | Build toolchain. |
|
||||||
|
|
||||||
|
## Bundled assets
|
||||||
|
|
||||||
|
| Asset | Licence | Source |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Exo 2 (`HellionFont.ttf`) | SIL Open Font License 1.1 | [Google Fonts / Natanael Gama](https://fonts.google.com/specimen/Exo+2). The OFL licence text travels embedded next to the font (`HellionFont-OFL.txt`) to satisfy the "licence must be distributed with the font" clause. |
|
||||||
|
| Hellion plugin icon (`images/icon.png`) | © Hellion Media, included under the project licence (EUPL-1.2). | Original artwork. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Upstream code
|
||||||
|
|
||||||
|
HellionChat is a fork of [Chat 2](https://github.com/Infiziert90/ChatTwo)
|
||||||
|
by Infiziert90 (Infi) and Anna Clemens, also licensed under EUPL-1.2.
|
||||||
|
The bulk of the code, including the message store architecture, the
|
||||||
|
channel logic, the hook system and the ImGui chat window, originates
|
||||||
|
from upstream. See `NOTICE.md` and `UPSTREAM_SYNC.md` for the
|
||||||
|
attribution and the cherry-pick policy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components that touch the network
|
||||||
|
|
||||||
|
Of everything listed above, **none** of the bundled or NuGet
|
||||||
|
components opens network connections on their own. All outbound
|
||||||
|
traffic is initiated explicitly by HellionChat's own source files
|
||||||
|
and is documented in `PRIVACY.md` under "Outbound network calls":
|
||||||
|
|
||||||
|
- `ChatTwo/EmoteCache.cs` → BetterTTV API + CDN (opt-out via setting)
|
||||||
|
- `ChatTwo/FontManager.cs` → Square Enix Lodestone font CDN (one-time
|
||||||
|
download)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verifying this list
|
||||||
|
|
||||||
|
To regenerate the dependency inventory after a version bump:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet list ChatTwo.sln package --include-transitive
|
||||||
|
```
|
||||||
|
|
||||||
|
The "direct NuGet dependencies" table above only lists direct
|
||||||
|
references. Transitive dependencies pulled in by Dalamud SDK or by
|
||||||
|
the listed packages are covered by the SDK / package licences and
|
||||||
|
documented by their respective maintainers.
|
||||||
|
|
||||||
|
To re-audit the network-call inventory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rn -E "HttpClient|HttpRequest|new Uri\(|https?://" \
|
||||||
|
--include="*.cs" ChatTwo/
|
||||||
|
```
|
||||||
|
|
||||||
|
Any new hit that is not a click-through (`Util.OpenLink`) or a
|
||||||
|
payload-parsing call must be added to `PRIVACY.md` before release.
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
"project_id" : "663694"
|
|
||||||
"base_path" : "."
|
|
||||||
"base_url" : "https://api.crowdin.com"
|
|
||||||
"preserve_hierarchy": true
|
|
||||||
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
"source" : "/ChatTwo/Resources/Language.resx",
|
|
||||||
"translation" : "/ChatTwo/Resources/Language.%two_letters_code%.resx",
|
|
||||||
"dest" : "/Language.resx",
|
|
||||||
"skip_untranslated_strings": true,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"Author": "JonKazama-Hellion",
|
"Author": "JonKazama-Hellion",
|
||||||
"Name": "Hellion Chat",
|
"Name": "Hellion Chat",
|
||||||
"InternalName": "HellionChat",
|
"InternalName": "HellionChat",
|
||||||
"AssemblyVersion": "0.5.4.0",
|
"AssemblyVersion": "0.6.0.0",
|
||||||
"Description": "Hellion Chat is built on top of Chat 2 with one removal and a stack\nof privacy controls on top. Tabs, channel filters, RGB colours,\nemotes, screenshot mode, IPC integration and the chat replacement\nwindow itself work the same. The optional webinterface that Chat 2\nships is intentionally not part of this fork because it serves a\ndifferent use case from the smaller default footprint Hellion Chat\nis built around.\n\nOn top of that, Hellion Chat adds privacy and data-handling controls\ndesigned to align with the modern data protection rules that apply\nacross the EU, the United States and Japan. By default only your own\nconversations are stored; messages from strangers, NPCs and system\nspam stay out of the database. Retention windows are configurable per\nchannel, history can be wiped retroactively, and stored data can be\nexported on demand.\n\nKey additions on top of Chat 2:\n\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with a Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three preset profiles (Privacy-First, Casual,\n Full History)\n- Bilingual UI (English and German) with live language switching\n- Independent plugin state — own config file and database directory,\n so Hellion Chat does not share state with the upstream plugin\n\nBased on Chat 2 by Infi and Anna, licensed under EUPL-1.2.",
|
"Description": "Hellion Chat is built on top of Chat 2 with one removal and a stack\nof privacy controls on top. Tabs, channel filters, RGB colours,\nemotes, screenshot mode, IPC integration and the chat replacement\nwindow itself work the same. The optional webinterface that Chat 2\nships is intentionally not part of this fork because it serves a\ndifferent use case from the smaller default footprint Hellion Chat\nis built around.\n\nOn top of that, Hellion Chat adds privacy and data-handling controls\ndesigned to align with the modern data protection rules that apply\nacross the EU, the United States and Japan. By default only your own\nconversations are stored; messages from strangers, NPCs and system\nspam stay out of the database. Retention windows are configurable per\nchannel, history can be wiped retroactively, and stored data can be\nexported on demand.\n\nKey additions on top of Chat 2:\n\n- Channel whitelist with a Privacy-First default\n- Per-channel retention with a daily background sweep\n- Retroactive cleanup with a Ctrl+Shift confirm\n- Export to Markdown, JSON or CSV\n- First-run wizard with three preset profiles (Privacy-First, Casual,\n Full History)\n- Bilingual UI (English and German) with live language switching\n- Independent plugin state — own config file and database directory,\n so Hellion Chat does not share state with the upstream plugin\n\nBased on Chat 2 by Infi and Anna, licensed under EUPL-1.2.",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"RepoUrl": "https://github.com/JonKazama-Hellion/HellionChat",
|
"RepoUrl": "https://github.com/JonKazama-Hellion/HellionChat",
|
||||||
@@ -20,12 +20,12 @@
|
|||||||
"CanUnloadAsync": false,
|
"CanUnloadAsync": false,
|
||||||
"LoadPriority": 0,
|
"LoadPriority": 0,
|
||||||
"Punchline": "Chat 2 with privacy controls aligned to EU, US and JP rules",
|
"Punchline": "Chat 2 with privacy controls aligned to EU, US and JP rules",
|
||||||
"Changelog": "**Hellion Chat 0.5.4 — WrapText hardening**\n\nReplaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with\nSpan- and index-based control flow. Closes the persistent CodeQL\nCritical alert \"unvalidated local pointer arithmetic\" that kept\nre-firing on every shape of the previous fix.\n\nHardening:\n\n- WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount\n via ArrayPool, validates the actual encoded length against that\n ceiling, and threads the rest of the algorithm through int offsets\n instead of raw byte pointers\n- Pointer arithmetic only happens inside two small private helpers\n (CalcWordWrap and DrawText) that take the pinned base pointer plus\n int offsets sourced from the plugin's own logic, not from any\n virtual-method return\n- Added a 16 KiB upper bound on the buffer rent to prevent a\n pathological input from triggering an unbounded ArrayPool allocation\n\nNo user-visible behaviour change. Word-wrap output is byte-identical\nto v0.5.3.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.3 — Pointer arithmetic hardening**\n\nClosed CodeQL Critical alert in ImGuiUtil.WrapText by validating the\nencoded byte buffer length via GetByteCount before pointer\narithmetic. Single-fix patch on top of v0.5.2.\n\n**Hellion Chat 0.5.2 — Bugfix patch**\n\nAuto-Tell-Tabs history-separator landed below the live tell instead\nof above (preload now excludes the trigger message). Plugin icon\npackaging fixed by removing a stale DalamudPackager.targets override\nthat conflicted with the SDK 15 default. Default config aligned to\nthe maintainer's daily driver: HellionThemeWindowOpacity 0.5,\nUse24HourClock true, Gruppe tab no longer auto-routes /party. Two\nearlier CodeQL findings closed (workflow permissions, empty-input\npointer arithmetic).\n\n**Hellion Chat 0.5.1 — Backlog Sweep**\n\nPure hardening and polish. Eight backlog items from the v0.5.0\ncodebase review collected into one patch: cleanup-preview-stale\ndetection, greeted-tab dim background, Performance HelpMarker\nconsistency, Tabs/Database tab names from HellionStrings,\nFontChooser framework-thread marshalling, async-void on\nEmoteCache.LoadData, parameterised SQL via BindIntList helper.\n\n---\n\nEarlier history at https://github.com/JonKazama-Hellion/HellionChat/releases.",
|
"Changelog": "**Hellion Chat 0.6.0 — UX Polish: Pop-Out Input + Colour Presets**\n\nTwo opt-in UX features land in the same release. Existing users see\nno change unless they enable the new toggles.\n\nPop-out input bar:\n\n- New global master switch in Settings → Window → Frame: \"Enable input\n in pop-outs\". Default OFF so existing behaviour is preserved\n- When enabled, every pop-out window grows a compact input bar at the\n bottom (channel-coloured icon button left, text input right). The\n auto-translate picker is intentionally not part of the compact bar\n in v0.6.0 — typical pop-out workflows (FC greeter, club hostess)\n rarely need it there\n- Each pop-out keeps an independent text buffer and history cursor;\n channel changes still apply globally because that is how the FFXIV\n channel API works\n- Up/Down navigates a shared input history singleton across the main\n window and every open pop-out\n- First pop-out opening after the upgrade shows a one-time hint\n banner pointing users to the new toggle\n\nChat colour presets:\n\n- Seven built-in presets above the per-channel colour list in\n Settings → Appearance → Colours: ChatTwo Default, High-Contrast,\n Pastell, Dark-Mode-Tuned, Hellion (brand-coloured, blue/orange\n Arctic Cyan + Ember Glow palette from the Hellion Online Media\n branding spec), plus two bonus mood presets — Night Blue (royal\n blue, classic-cool) and Indigo Violet (royal violet, glitter-mystic)\n- Apply is immediate and overwrites the channels covered by the\n preset; battle-channel colours are left alone so combat tuning\n stays intact\n\nConfiguration migrates from v10 to v11 with a diagnostic log entry;\nno data is reset. Bilingual (English/German) for both new sections.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.4 — WrapText hardening**\n\nReplaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with\nSpan- and index-based control flow. Closes the persistent CodeQL\nCritical alert \"unvalidated local pointer arithmetic\" that kept\nre-firing on every shape of the previous fix.\n\nHardening:\n\n- WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount\n via ArrayPool, validates the actual encoded length against that\n ceiling, and threads the rest of the algorithm through int offsets\n instead of raw byte pointers\n- Pointer arithmetic only happens inside two small private helpers\n (CalcWordWrap and DrawText) that take the pinned base pointer plus\n int offsets sourced from the plugin's own logic, not from any\n virtual-method return\n- Added a 16 KiB upper bound on the buffer rent to prevent a\n pathological input from triggering an unbounded ArrayPool allocation\n\nNo user-visible behaviour change. Word-wrap output is byte-identical\nto v0.5.3.\n\nBased on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).\n\n**Hellion Chat 0.5.3 — Pointer arithmetic hardening**\n\nClosed CodeQL Critical alert in ImGuiUtil.WrapText by validating the\nencoded byte buffer length via GetByteCount before pointer\narithmetic. Single-fix patch on top of v0.5.2.\n\n**Hellion Chat 0.5.2 — Bugfix patch**\n\nAuto-Tell-Tabs history-separator landed below the live tell instead\nof above (preload now excludes the trigger message). Plugin icon\npackaging fixed by removing a stale DalamudPackager.targets override\nthat conflicted with the SDK 15 default. Default config aligned to\nthe maintainer's daily driver: HellionThemeWindowOpacity 0.5,\nUse24HourClock true, Gruppe tab no longer auto-routes /party. Two\nearlier CodeQL findings closed (workflow permissions, empty-input\npointer arithmetic).\n\n**Hellion Chat 0.5.1 — Backlog Sweep**\n\nPure hardening and polish. Eight backlog items from the v0.5.0\ncodebase review collected into one patch: cleanup-preview-stale\ndetection, greeted-tab dim background, Performance HelpMarker\nconsistency, Tabs/Database tab names from HellionStrings,\nFontChooser framework-thread marshalling, async-void on\nEmoteCache.LoadData, parameterised SQL via BindIntList helper.\n\n---\n\nEarlier history at https://github.com/JonKazama-Hellion/HellionChat/releases.",
|
||||||
"AcceptsFeedback": true,
|
"AcceptsFeedback": true,
|
||||||
"DownloadLinkInstall": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.4/latest.zip",
|
"DownloadLinkInstall": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.6.0/latest.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.4/latest.zip",
|
"DownloadLinkUpdate": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.6.0/latest.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.5.4/latest.zip",
|
"DownloadLinkTesting": "https://github.com/JonKazama-Hellion/HellionChat/releases/download/v0.6.0/latest.zip",
|
||||||
"TestingAssemblyVersion": "0.5.4.0",
|
"TestingAssemblyVersion": "0.6.0.0",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png",
|
"IconUrl": "https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/icon.png",
|
||||||
"ImageUrls": [
|
"ImageUrls": [
|
||||||
"https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/chatWindow.png",
|
"https://raw.githubusercontent.com/JonKazama-Hellion/HellionChat/main/ChatTwo/images/chatWindow.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user