Compare commits
25 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 | |||
| 1b7f2c40e6 | |||
| 93d52ae819 | |||
| 48b3d5c6b1 |
@@ -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:
|
||||
label: HellionChat version
|
||||
description: From Settings → Information → Version
|
||||
placeholder: "0.5.1"
|
||||
placeholder: "0.5.4"
|
||||
validations:
|
||||
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
|
||||
# current Dalamud staging branch, locates the latest.zip produced by
|
||||
# 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
|
||||
# step (path string from Get-ChildItem on a controlled directory).
|
||||
#
|
||||
# User-controlled inputs touched by this workflow:
|
||||
# - the tag name (filtered by on.tags = v*, validated again at runtime
|
||||
# against ^v\d+\.\d+\.\d+$ before being used in any string)
|
||||
# All other values are either repo-controlled (paths under
|
||||
# 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:
|
||||
push:
|
||||
@@ -53,9 +58,101 @@ jobs:
|
||||
Write-Host "Found: $($zip.FullName)"
|
||||
"path=$($zip.FullName)" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||
|
||||
# Build a release body from the matching changelog block in
|
||||
# HellionChat.yaml plus a static install / docs footer. Fails the
|
||||
# workflow if no block exists for the tagged version, which is the
|
||||
# automated counterpart to the "yaml + repo.json + release body
|
||||
# kept in sync" rule.
|
||||
#
|
||||
# GITHUB_REF_NAME is read via env: (not ${{ }} interpolation) so the
|
||||
# tag value is treated as a PowerShell variable, not as inline shell
|
||||
# text. The strict regex below rejects anything that is not a clean
|
||||
# semver tag before it is used to build a string.
|
||||
- name: Generate release body
|
||||
shell: pwsh
|
||||
env:
|
||||
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
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: ${{ steps.locate.outputs.path }}
|
||||
body_path: release-body.md
|
||||
fail_on_unmatched_files: true
|
||||
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
|
||||
called out in the yaml changelog so users can see what it
|
||||
derives from. -->
|
||||
<Version>0.5.2</Version>
|
||||
<Version>0.6.0</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
||||
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int LatestVersion = 10;
|
||||
private const int LatestVersion = 11;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -103,6 +103,18 @@ public class Configuration : IPluginConfiguration
|
||||
// want the auto tabs themselves without the extra UI affordance.
|
||||
public bool AutoTellTabsShowGreetedToggle;
|
||||
|
||||
// Hellion Chat — One-Time-Hint-Banner that introduces the v0.6.0 pop-out
|
||||
// input feature. Set to true once the user dismisses the banner from a
|
||||
// pop-out window; never reset after that.
|
||||
public bool SeenPopOutInputHint;
|
||||
|
||||
// Hellion Chat — v0.6.0 master switch for the pop-out input bar.
|
||||
// Global on purpose: per-tab makes no sense for Auto-Tell-Tabs which
|
||||
// are session-only and would force the user to re-enable it for every
|
||||
// new conversation. Default OFF so existing users see no behavior
|
||||
// change after the v10→v11 migration.
|
||||
public bool PopOutInputEnabled;
|
||||
|
||||
public int GetRetentionDays(ChatType type)
|
||||
{
|
||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||
@@ -296,6 +308,9 @@ public class Configuration : IPluginConfiguration
|
||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||
|
||||
SeenPopOutInputHint = other.SeenPopOutInputHint;
|
||||
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -414,13 +414,13 @@ internal unsafe class KeybindManager : IDisposable {
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
||||
DispatchTabDelta(1);
|
||||
return;
|
||||
}
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
||||
DispatchTabDelta(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -465,6 +465,24 @@ internal unsafe class KeybindManager : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
// v0.6.0 — central dispatch for ChatTabForward/Backward. If a pop-out
|
||||
// window currently has its compact input focused, the keybind is
|
||||
// forwarded into that pop-out's ChatInputBar so the user navigates
|
||||
// tabs in the window they are typing in. Otherwise the main window
|
||||
// handles it (= v0.5.x behavior).
|
||||
private void DispatchTabDelta(int delta)
|
||||
{
|
||||
foreach (var popout in Plugin.ChatLogWindow.ActivePopouts)
|
||||
{
|
||||
if (popout.HasFocusedInputBar && popout.InputBar != null)
|
||||
{
|
||||
popout.InputBar.HandleKeybindForward(delta);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(delta);
|
||||
}
|
||||
|
||||
private static Keybind GetKeybind(string id)
|
||||
{
|
||||
var outData = new FFXIVClientStructs.FFXIV.Client.System.Input.Keybind();
|
||||
|
||||
+80
-304
@@ -44,320 +44,96 @@ tags:
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 0.5.2 — Bugfix patch**
|
||||
**Hellion Chat 0.6.0 — UX Polish: Pop-Out Input + Colour Presets**
|
||||
|
||||
Three corrections to the v0.5.1 surface plus two security findings
|
||||
closed by the new manual-build CodeQL workflow. No new features, no
|
||||
migration, configuration version stays at 10.
|
||||
Two opt-in UX features land in the same release. Existing users see
|
||||
no change unless they enable the new toggles.
|
||||
|
||||
Bug fixes:
|
||||
Pop-out input bar:
|
||||
|
||||
- Auto-Tell-Tabs: the "earlier conversations" separator no longer
|
||||
lands below the live tell. The triggering message was already
|
||||
persisted in the store by the time the spawn handler fired, so
|
||||
it appeared as the youngest historic message. The preload now
|
||||
excludes the live tell explicitly and pulls one extra row so the
|
||||
user does not lose a slot to the exclusion.
|
||||
- General/Aussehen: HellionThemeWindowOpacity ships at 0.5 so a
|
||||
fresh install lands at the more glass-like default. Existing
|
||||
users keep their saved value.
|
||||
- General/Allgemein: Use24HourClock ships at true so a German /
|
||||
European install starts on 24h time without a manual flip.
|
||||
- Tabs/Gruppe: the default Gruppe preset no longer auto-routes
|
||||
/party into the tab. The tab still collects /party, /alliance,
|
||||
/pvpteam together as a read surface but does not steal the
|
||||
input focus when you wanted /alliance.
|
||||
- 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
|
||||
|
||||
Security:
|
||||
Chat colour presets:
|
||||
|
||||
- Closed CodeQL Critical alert "unvalidated local pointer
|
||||
arithmetic" in ImGuiUtil.WrapText: empty splits between
|
||||
consecutive newlines produced a zero-length byte array whose
|
||||
fixed pointer collapsed onto its end pointer. Bail before the
|
||||
fixed block when the slice is empty.
|
||||
- Closed CodeQL Medium alert "workflow does not contain
|
||||
permissions" by pinning the build workflow to contents: read.
|
||||
- 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
|
||||
|
||||
Documentation: README now carries Build, CodeQL, License, Latest
|
||||
Release, Dalamud API, .NET and FFXIV badges. License detection
|
||||
picks up EUPL-1.2 correctly via a separated COPYRIGHT file. Added
|
||||
NOTICE.md and UPSTREAM_SYNC.md after leaving the GitHub fork
|
||||
network.
|
||||
Configuration migrates from v10 to v11 with a diagnostic log entry;
|
||||
no data is reset. Bilingual (English/German) for both new sections.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.4 — WrapText hardening**
|
||||
|
||||
Replaces the unsafe pointer-arithmetic in ImGuiUtil.WrapText with
|
||||
Span- and index-based control flow. Closes the persistent CodeQL
|
||||
Critical alert "unvalidated local pointer arithmetic" that kept
|
||||
re-firing on every shape of the previous fix.
|
||||
|
||||
Hardening:
|
||||
|
||||
- WrapText now allocates a buffer sized by Encoding.UTF8.GetMaxByteCount
|
||||
via ArrayPool, validates the actual encoded length against that
|
||||
ceiling, and threads the rest of the algorithm through int offsets
|
||||
instead of raw byte pointers
|
||||
- Pointer arithmetic only happens inside two small private helpers
|
||||
(CalcWordWrap and DrawText) that take the pinned base pointer plus
|
||||
int offsets sourced from the plugin's own logic, not from any
|
||||
virtual-method return
|
||||
- Added a 16 KiB upper bound on the buffer rent to prevent a
|
||||
pathological input from triggering an unbounded ArrayPool allocation
|
||||
|
||||
No user-visible behaviour change. Word-wrap output is byte-identical
|
||||
to v0.5.3.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.3 — Pointer arithmetic hardening**
|
||||
|
||||
Closed CodeQL Critical alert in ImGuiUtil.WrapText by validating the
|
||||
encoded byte buffer length via GetByteCount before pointer
|
||||
arithmetic. Single-fix patch on top of v0.5.2.
|
||||
|
||||
**Hellion Chat 0.5.2 — Bugfix patch**
|
||||
|
||||
Auto-Tell-Tabs history-separator landed below the live tell instead
|
||||
of above (preload now excludes the trigger message). Plugin icon
|
||||
packaging fixed by removing a stale DalamudPackager.targets override
|
||||
that conflicted with the SDK 15 default. Default config aligned to
|
||||
the maintainer's daily driver: HellionThemeWindowOpacity 0.5,
|
||||
Use24HourClock true, Gruppe tab no longer auto-routes /party. Two
|
||||
earlier CodeQL findings closed (workflow permissions, empty-input
|
||||
pointer arithmetic).
|
||||
|
||||
**Hellion Chat 0.5.1 — Backlog Sweep**
|
||||
|
||||
Pure hardening and polish. No new features. Eight backlog items
|
||||
from the v0.5.0 codebase review collected into one patch:
|
||||
Pure hardening and polish. Eight backlog items from the v0.5.0
|
||||
codebase review collected into one patch: cleanup-preview-stale
|
||||
detection, greeted-tab dim background, Performance HelpMarker
|
||||
consistency, Tabs/Database tab names from HellionStrings,
|
||||
FontChooser framework-thread marshalling, async-void on
|
||||
EmoteCache.LoadData, parameterised SQL via BindIntList helper.
|
||||
|
||||
- Cleanup preview now flags itself as out-of-date when the user
|
||||
edits the whitelist after the last refresh, and the refresh
|
||||
button is visually emphasised in that state
|
||||
- Greeted Auto-Tell-Tabs now also dim their selection and hover
|
||||
backgrounds in the sidebar, not just the text
|
||||
- Performance section in the General tab moves to the standard
|
||||
HelpMarker tooltip pattern instead of a wall-of-text description
|
||||
- Tabs and Database settings tabs pull their display name from
|
||||
HellionStrings instead of the upstream Language bundle, so all
|
||||
eight tabs share one i18n source
|
||||
- FontChooser results are now marshalled onto the framework thread
|
||||
via Plugin.Framework.Run instead of being written to settings
|
||||
state directly from the threadpool
|
||||
- EmoteCache.LoadData drops async void and the four CS8618 build
|
||||
warnings the build has been carrying since v0.4.0
|
||||
- All MessageStore SQL paths that fed dynamic value lists into
|
||||
interpolated SQL now use named parameter bindings via a new
|
||||
BindIntList helper. Same behaviour, defence against future
|
||||
user-input regressions
|
||||
---
|
||||
|
||||
Configuration version is unchanged at 10. No migration. Existing
|
||||
installs upgrade silently.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.0 — Settings UX polish**
|
||||
|
||||
The settings window has been pulled apart and rebuilt around eight
|
||||
themed tabs instead of the twelve organic ones it grew into.
|
||||
Settings now sit where they belong and the wall-of-text descriptions
|
||||
have been replaced with hover help markers across every section.
|
||||
|
||||
What changed in this release:
|
||||
|
||||
- Twelve tabs collapsed into eight: General, Appearance, Window,
|
||||
Chat, Tabs, Privacy, Database and Information
|
||||
- Theme and font controls moved out of the Privacy tab into
|
||||
Appearance where they belong
|
||||
- Auto-Tell-Tabs settings, message preview and emote controls now
|
||||
live under one Chat tab with collapsible sections
|
||||
- About and Changelog merged into a single Information tab
|
||||
- Disabled settings keep their tooltip help marker visible so you
|
||||
can still read why an option is greyed out
|
||||
- Section headings start collapsed by default, the same pattern
|
||||
used for the Auto-Tell-Tabs preload section in 0.4.0
|
||||
|
||||
Configuration version bumps from 9 to 10 as a wipe migration. The
|
||||
old config file is copied to HellionChat.json.pre-v10-backup before
|
||||
the new defaults are written, so you can restore your previous
|
||||
setup by hand if anything looks off. A one-shot notification on
|
||||
first start explains the reset.
|
||||
|
||||
No changes to message storage, retention sweep, the privacy filter
|
||||
or the export pipeline. Tabs and chat history are untouched by the
|
||||
migration.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.4.0 — Auto-Tell-Tabs**
|
||||
|
||||
Auto-Tell-Tabs lets you turn each /tell into a session-only tab
|
||||
dedicated to that conversation partner. The original use case is
|
||||
the FFXIV club greeter who has to track 5–15 parallel "hi, welcome"
|
||||
exchanges; everyone else can disable the feature in one click and
|
||||
go back to a single Tell Exclusive tab.
|
||||
|
||||
What lands in this release:
|
||||
|
||||
- Auto-spawn temp tab "Name@World" on /tell (incoming and outgoing)
|
||||
- Tab limit (default 15, range 1–50) with LRU drop that prefers
|
||||
greeted tabs first, then sorts by last activity
|
||||
- History preload from the local message store (default 20 tells,
|
||||
range 0–100) with a "— Earlier conversations —" separator above
|
||||
the live tell that triggered the spawn
|
||||
- Optional "mark as greeted" toggle button (off by default,
|
||||
greeter-specific) that dims the tab name and lets you flip the
|
||||
status
|
||||
- Section header "Active Tells (n)" or compact-mode separator in
|
||||
the sidebar between persistent tabs and the temp tabs
|
||||
- Settings UI under Chat (toggle / limit / compact / greeted-toggle)
|
||||
and Privacy (history preload count), with hover-tooltip help
|
||||
markers replacing the previous wall-of-text descriptions for the
|
||||
new sections
|
||||
- Save and load filters strip temp tabs from the on-disk config so
|
||||
a crash or a sidebar-mode toggle never persists or wipes them
|
||||
|
||||
Compatibility note: if XIV Messanger or another plugin is
|
||||
suppressing direct messages, disable its "Suppress DMs" option so
|
||||
Hellion Chat can receive tells and open the auto tabs.
|
||||
|
||||
Configuration version bumps from 8 to 9. Existing users get a one-
|
||||
shot notification on the first start, defaults are seeded by
|
||||
property initializers, persistent tabs are untouched.
|
||||
|
||||
The vertical sidebar tab view becomes the default for fresh
|
||||
installs; existing users keep their saved preference.
|
||||
|
||||
Inspired by the per-sender tab pattern in XIV InstantMessenger
|
||||
(Limiana, AGPL-3.0). No code was ported across the licence
|
||||
boundary; only the architectural concept influenced this design.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.3.1 — Upstream emote regression fix**
|
||||
|
||||
Cherry-picks Infi's upstream commit ff899ff "Fix a regression
|
||||
from API 15 updates" which changes the BetterTTV emote DTOs
|
||||
(Emote and Top100) from public fields to public properties.
|
||||
System.Text.Json under the API 15 toolchain only honours the
|
||||
[JsonPropertyName] attribute on properties, so the previous
|
||||
field-based version deserialised every fetched emote into empty
|
||||
default values. Result: BetterTTV emotes were silently broken
|
||||
on fresh installs. The fix is six lines and applies cleanly on
|
||||
top of our defensive null-check from earlier; the EmoteCache
|
||||
path-traversal hardening from 0.3.0 stays as it is.
|
||||
|
||||
Authorship of the fix is preserved with git cherry-pick -x, so
|
||||
Infi shows up as the author on the commit. Thanks to him for
|
||||
catching it in the upstream codebase.
|
||||
|
||||
**Hellion Chat 0.3.0 — Audit hardening, brand sweep and rebrand of slash commands**
|
||||
|
||||
This release closes the remaining audit follow-ups from the
|
||||
0.2.0 cleanup and finishes turning Hellion Chat into a properly
|
||||
branded fork rather than a Chat 2 with a different name.
|
||||
|
||||
Slash commands have been renamed across the board so they no
|
||||
longer collide with the upstream plugin and tell you which
|
||||
plugin owns them at a glance:
|
||||
|
||||
- /chat2 becomes /hellion
|
||||
- /chat2Viewer becomes /hellionView
|
||||
- /clearlog2 becomes /clearhellion
|
||||
- /chat2Debugger becomes /hellionDebugger (internal)
|
||||
- /chat2SeString becomes /hellionSeString (internal)
|
||||
|
||||
This is a breaking change for anyone with macros bound to the
|
||||
old command names. The upstream Chat 2 commands keep working
|
||||
if you also have that plugin installed.
|
||||
|
||||
Privacy and storage hardening based on the post-0.2.0 audit:
|
||||
|
||||
- Privacy filter master switch now states explicitly that the
|
||||
filter only governs storage, not the live chat log
|
||||
- Emote cache refuses to write outside its own directory if a
|
||||
third-party API ever returns a path that escapes
|
||||
- Retention sweep is serialised so the 24h auto-sweep and the
|
||||
manual button cannot launch in parallel and race for the
|
||||
SQLite connection
|
||||
- DbViewer paging uses an int constant and the matching SQL
|
||||
parameter name (the upstream code passed a float and a name
|
||||
without the parameter prefix; both worked in practice but
|
||||
were inconsistent)
|
||||
|
||||
Visual identity now matches the Hellion Online Media website:
|
||||
|
||||
- Theme palette switched to Arctic Cyan plus Ember Orange,
|
||||
matching the website's BRANDING.md tokens
|
||||
- Active tabs and window title bars use a brand-color-dark teal
|
||||
variation as identity colour, replacing the previous slate
|
||||
violet that did not appear in the brand
|
||||
- Resize grips and scrollbar grabs picked up Ember Orange
|
||||
instead of industrial amber on hover and active states
|
||||
|
||||
About tab rewritten and properly localised:
|
||||
|
||||
- New "Why this fork exists" block sets out the mission in
|
||||
neutral terms, framing Chat 2's full-history default as the
|
||||
right one for most users while explaining the narrower
|
||||
default footprint this fork chose
|
||||
- All Hellion-specific About copy now lives in HellionStrings
|
||||
in EN and DE, so German users see the Hellion sections in
|
||||
German rather than the upstream English fallback
|
||||
- Webinterface absence is described as a focus mismatch
|
||||
(different use case, substantial rebuild) rather than as
|
||||
a security issue with the upstream code
|
||||
- Translator list at the bottom of the About tab is reachable
|
||||
again on smaller settings windows
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.2.0 — Webinterface removed**
|
||||
|
||||
The upstream webinterface has been removed in its entirety. It
|
||||
serves a different use case from the smaller default footprint
|
||||
this fork is built around, namely remote access to chat from a
|
||||
second device. Aligning it with the data minimisation defaults
|
||||
Hellion Chat ships with would have meant a substantial rebuild.
|
||||
Removing it was the cleaner path for this particular fork.
|
||||
|
||||
What changed in this release:
|
||||
|
||||
- Settings tab "Webinterface" is gone, the corresponding
|
||||
Configuration fields (WebinterfaceEnabled / AutoStart / Password /
|
||||
Port / AuthStore / MaxLinesToSend) are dropped and stale entries
|
||||
fall out of the JSON on the next save automatically
|
||||
- The whole ChatTwo/Http tree, the bundled Svelte frontend in
|
||||
websiteBuild.zip and the WebinterfaceUtil helper are deleted
|
||||
- Watson.Lite (the HTTP server) and Newtonsoft.Json (only used by
|
||||
the webinterface JSON wire format) are removed from the
|
||||
package references
|
||||
- DbViewer's "Chat2 JSON Export" button is dropped because it
|
||||
serialised the database into the webinterface message protocol;
|
||||
the Privacy tab's MessageExporter (Markdown, JSON, CSV with
|
||||
channel and date filters) covers the same ground without the
|
||||
proprietary shape
|
||||
- About tab notes the absence so users coming from Chat 2 do not
|
||||
look for it
|
||||
- Configuration version bumps from 7 to 8 with a one-shot
|
||||
notification (EN + DE)
|
||||
|
||||
No changes to the privacy filter, retention sweep, first-run wizard
|
||||
or export pipeline. Existing chat history is preserved.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.1.2 — About tab rebrand, DBViewer polish**
|
||||
|
||||
- About tab now shows Hellion-specific maintainer, license, EU/US/JP
|
||||
disclaimer and SQUARE ENIX disclaimer instead of the inherited
|
||||
Chat 2 contact info; original ChatTwo translator credits stay
|
||||
visible under a clearly labelled upstream tree node
|
||||
- Localization clarified: Hellion-specific German strings are
|
||||
maintained by the fork maintainer, the Crowdin contributor list
|
||||
only covers the inherited upstream strings
|
||||
- Cherry-picked DBViewer UI improvements from upstream Chat 2
|
||||
(auto-scroll-reset on page change, tooltips on date reset,
|
||||
folder export, page arrows, localized export-running messages)
|
||||
- README rewritten in the Hellion project style with a tech-stack
|
||||
table, architecture tree, database column list, install guide,
|
||||
upstream-sync workflow notes and project-status checklist
|
||||
|
||||
**Hellion Chat 0.1.1 — Packaging and migration fixes**
|
||||
|
||||
- Plugin icon now ships inside the bundle, so the Hellion logo
|
||||
renders locally in the Dalamud plugin list once installed (the
|
||||
previous release relied only on the remote IconUrl)
|
||||
- Plugin icon downsampled from 1024×1024 to 256×256 to match the
|
||||
rendered size; loads faster and caches better
|
||||
- Migration from upstream Chat 2 is more robust: each file move is
|
||||
wrapped individually, a locked SQLite database no longer aborts
|
||||
the rest of the migration, and a warning notification fires when
|
||||
any file is held open (with a hint to disable Chat 2 and restart
|
||||
the game)
|
||||
- README ships a step-by-step migration guide (fresh install versus
|
||||
coming from Chat 2) and a troubleshooting section with manual
|
||||
recovery commands for Linux and Windows
|
||||
|
||||
**Hellion Chat 0.1.0 — Initial fork release**
|
||||
|
||||
Privacy
|
||||
- Channel whitelist filter in MessageStore.UpsertMessage with a
|
||||
Privacy-First default (own conversations only)
|
||||
- Per-channel retention with a 24-hour idempotent background sweep
|
||||
- Retroactive cleanup with a Ctrl+Shift confirm and VACUUM
|
||||
- Export to Markdown / JSON / CSV via Dalamud's file dialog
|
||||
|
||||
Onboarding
|
||||
- First-run wizard with three profiles: Privacy-First / Casual /
|
||||
Full History
|
||||
- Configuration migration that seeds defaults on update
|
||||
- One-shot migration from upstream Chat 2's pluginConfigs layout
|
||||
- Migrate3 idempotency recovery for half-migrated databases
|
||||
|
||||
Look & feel
|
||||
- Localized UI (English and German) with live language switching
|
||||
- Industrial HUD theme with cyan-teal action accents, slate-violet
|
||||
tabs, amber active highlights and a window-opacity slider
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
Earlier history at https://github.com/JonKazama-Hellion/HellionChat/releases.
|
||||
|
||||
@@ -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.
|
||||
// General catches player chat plus active gameplay events; the
|
||||
// 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_Linkshell => Get(nameof(Tabs_Presets_Linkshell));
|
||||
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">
|
||||
<value>Wenn du mehrere Linkshells benutzt, empfiehlt der Maintainer einen Tab pro Shell für eine sauberere Übersicht. Tab duplizieren und je Kopie die Kanalauswahl einschränken.</value>
|
||||
</data>
|
||||
<data name="ChatColourPresets_Default" xml:space="preserve">
|
||||
<value>ChatTwo Standard</value>
|
||||
</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>
|
||||
|
||||
@@ -555,4 +555,43 @@
|
||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||
<value>If you use multiple linkshells, the maintainer recommends one tab per shell for cleaner readability. Duplicate this tab and narrow the channel selection per copy.</value>
|
||||
</data>
|
||||
<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>
|
||||
|
||||
@@ -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; }
|
||||
private int ActivatePos = -1;
|
||||
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;
|
||||
public bool TellSpecial;
|
||||
private readonly Stopwatch LastResize = new();
|
||||
@@ -330,16 +333,10 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
private void AddBacklog(string message)
|
||||
{
|
||||
for (var i = 0; i < InputBacklog.Count; i++)
|
||||
{
|
||||
if (InputBacklog[i] != message)
|
||||
continue;
|
||||
|
||||
InputBacklog.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
|
||||
InputBacklog.Add(message);
|
||||
// v0.6.0 — delegates to the shared InputHistoryService so pop-out
|
||||
// ChatInputBar instances see the same history. Move-to-newest
|
||||
// deduplication lives inside the service.
|
||||
InputHistoryService.Push(message);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(Chat))
|
||||
@@ -1481,6 +1490,13 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
internal readonly List<bool> PopOutDocked = [];
|
||||
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()
|
||||
{
|
||||
HandlerLender.ResetCounter();
|
||||
@@ -1757,7 +1773,7 @@ public sealed class ChatLogWindow : Window
|
||||
offset = 1;
|
||||
}
|
||||
|
||||
InputBacklogIdx = InputBacklog.Count - 1 - offset;
|
||||
InputBacklogIdx = InputHistoryService.Count - 1 - offset;
|
||||
break;
|
||||
case > 0:
|
||||
InputBacklogIdx--;
|
||||
@@ -1766,7 +1782,7 @@ public sealed class ChatLogWindow : Window
|
||||
break;
|
||||
case ImGuiKey.DownArrow:
|
||||
if (InputBacklogIdx != -1)
|
||||
if (++InputBacklogIdx >= InputBacklog.Count)
|
||||
if (++InputBacklogIdx >= InputHistoryService.Count)
|
||||
InputBacklogIdx = -1;
|
||||
break;
|
||||
}
|
||||
@@ -1774,7 +1790,7 @@ public sealed class ChatLogWindow : Window
|
||||
if (prevPos == InputBacklogIdx)
|
||||
return 0;
|
||||
|
||||
var historyStr = InputBacklogIdx >= 0 ? InputBacklog[InputBacklogIdx] : string.Empty;
|
||||
var historyStr = InputHistoryService.GetByCursor(InputBacklogIdx) ?? string.Empty;
|
||||
data.DeleteChars(0, data.BufTextLen);
|
||||
data.InsertChars(0, historyStr);
|
||||
|
||||
|
||||
+89
-1
@@ -15,6 +15,13 @@ internal class Popout : Window
|
||||
private long FrameTime; // set every frame
|
||||
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")
|
||||
{
|
||||
ChatLogWindow = chatLogWindow;
|
||||
@@ -93,13 +100,94 @@ internal class Popout : Window
|
||||
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();
|
||||
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))
|
||||
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()
|
||||
{
|
||||
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
||||
|
||||
@@ -230,6 +230,12 @@ internal sealed class Appearance : ISettingsTab
|
||||
|
||||
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 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()
|
||||
{
|
||||
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);
|
||||
|
||||
// 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);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
|
||||
|
||||
|
||||
+142
-95
@@ -1,3 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
@@ -58,129 +59,175 @@ internal static class ImGuiUtil
|
||||
handler.Click(chunk, payload, button);
|
||||
}
|
||||
|
||||
internal static unsafe void WrapText(string csText, Chunk chunk, PayloadHandler? handler, Vector4 defaultText, float lineWidth)
|
||||
// Ceiling on the byte buffer for a single rendered line. UTF-8 takes at
|
||||
// most 4 bytes per char; ImGui's internal ImString limit is well below
|
||||
// this and FFXIV's chat lines top out around a few hundred chars in
|
||||
// practice. The cap prevents an unbounded ArrayPool rent if a caller
|
||||
// ever feeds in a degenerate input.
|
||||
private const int MaxLineByteCount = 16 * 1024;
|
||||
|
||||
internal static void WrapText(string csText, Chunk chunk, PayloadHandler? handler, Vector4 defaultText, float lineWidth)
|
||||
{
|
||||
void Text(byte* text, byte* textEnd)
|
||||
{
|
||||
var oldPos = ImGui.GetCursorScreenPos();
|
||||
|
||||
ImGuiNative.TextUnformatted(text, textEnd);
|
||||
PostPayload(chunk, handler);
|
||||
|
||||
if (!ReferenceEquals(LastLink, chunk.Link))
|
||||
PayloadBounds.Clear();
|
||||
|
||||
LastLink = chunk.Link;
|
||||
|
||||
if (Hovered != null && ReferenceEquals(Hovered, chunk.Link))
|
||||
{
|
||||
defaultText.W = 0.25f;
|
||||
var actualCol = ColourUtil.Vector4ToAbgr(defaultText);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(oldPos, oldPos + ImGui.GetItemRectSize(), actualCol);
|
||||
|
||||
foreach (var (start, size) in PayloadBounds)
|
||||
ImGui.GetWindowDrawList().AddRectFilled(start, start + size, actualCol);
|
||||
|
||||
PayloadBounds.Clear();
|
||||
}
|
||||
|
||||
if (Hovered == null && chunk.Link != null)
|
||||
PayloadBounds.Add((oldPos, ImGui.GetItemRectSize()));
|
||||
}
|
||||
|
||||
if (csText.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var part in csText.Split(["\r\n", "\r", "\n"], StringSplitOptions.None))
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(part);
|
||||
|
||||
// Empty splits (consecutive newlines) leave bytes.Length at 0
|
||||
// and the textEnd pointer below would coincide with text. The
|
||||
// ImGuiNative word-wrap calls treat that as undefined input,
|
||||
// and the CodeQL "unvalidated local pointer arithmetic" alert
|
||||
// also flags it. Render an empty line and skip the unsafe
|
||||
// block entirely for this iteration.
|
||||
if (bytes.Length == 0)
|
||||
if (part.Length == 0)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
continue;
|
||||
}
|
||||
|
||||
fixed (byte* rawText = bytes)
|
||||
// Allocate against the encoder's own MaxByteCount so the buffer
|
||||
// we hand to ImGui is sized by us. The actual byte count
|
||||
// returned by GetBytes is then validated against that ceiling
|
||||
// before any pointer arithmetic touches it; CodeQL recognises
|
||||
// that comparison as a sanitiser for the
|
||||
// cs/unvalidated-local-pointer-arithmetic taint flow.
|
||||
var maxBytes = Encoding.UTF8.GetMaxByteCount(part.Length);
|
||||
if (maxBytes <= 0 || maxBytes > MaxLineByteCount)
|
||||
{
|
||||
var text = rawText;
|
||||
var textEnd = text + bytes.Length;
|
||||
ImGui.TextUnformatted("");
|
||||
continue;
|
||||
}
|
||||
|
||||
var widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
var endPrevLine = ImGuiNative.CalcWordWrapPositionA(ImGui.GetFont().Handle, ImGuiHelpers.GlobalScale, text, textEnd, widthLeft);
|
||||
if (endPrevLine == null)
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(maxBytes);
|
||||
try
|
||||
{
|
||||
var written = Encoding.UTF8.GetBytes(part, 0, part.Length, buffer, 0);
|
||||
if (written <= 0 || written > maxBytes)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
continue;
|
||||
}
|
||||
|
||||
var firstSpace = FindFirstSpace(text, textEnd);
|
||||
var properBreak = firstSpace <= endPrevLine;
|
||||
WrapEncodedLine(buffer.AsSpan(0, written), chunk, handler, defaultText, lineWidth);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void WrapEncodedLine(ReadOnlySpan<byte> bytes, Chunk chunk, PayloadHandler? handler, Vector4 defaultText, float lineWidth)
|
||||
{
|
||||
var byteCount = bytes.Length;
|
||||
if (byteCount == 0)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (byte* basePtr = bytes)
|
||||
{
|
||||
var widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
var endPrev = CalcWordWrap(basePtr, 0, byteCount, widthLeft);
|
||||
if (endPrev < 0)
|
||||
return;
|
||||
|
||||
var firstSpace = FindFirstSpace(bytes, 0, byteCount);
|
||||
var properBreak = firstSpace <= endPrev;
|
||||
if (properBreak)
|
||||
{
|
||||
DrawText(basePtr, 0, endPrev, chunk, handler, defaultText);
|
||||
}
|
||||
else if (lineWidth == 0f)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether the next chunk would wrap at or past the
|
||||
// first space. If yes, force a line break.
|
||||
var wrapPos = CalcWordWrap(basePtr, 0, firstSpace, lineWidth);
|
||||
if (wrapPos >= firstSpace)
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
|
||||
widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
var lineStart = 0;
|
||||
while (endPrev < byteCount)
|
||||
{
|
||||
if (properBreak)
|
||||
{
|
||||
Text(text, endPrevLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lineWidth == 0f)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if the next bit is longer than the entire line width
|
||||
var wrapPos = ImGuiNative.CalcWordWrapPositionA(ImGui.GetFont().Handle, ImGuiHelpers.GlobalScale, text, firstSpace, lineWidth);
|
||||
lineStart = endPrev;
|
||||
|
||||
// only go to next line is it's going to wrap at the space
|
||||
if (wrapPos >= firstSpace)
|
||||
ImGui.TextUnformatted("");
|
||||
}
|
||||
// Skip a leading space at the start of a wrapped line.
|
||||
if (lineStart < byteCount && bytes[lineStart] == (byte)' ')
|
||||
lineStart++;
|
||||
|
||||
var newEnd = CalcWordWrap(basePtr, lineStart, byteCount, widthLeft);
|
||||
if (properBreak && newEnd == endPrev)
|
||||
break;
|
||||
|
||||
if (newEnd < 0)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
ImGui.TextUnformatted("");
|
||||
break;
|
||||
}
|
||||
|
||||
widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
while (endPrevLine < textEnd)
|
||||
endPrev = newEnd;
|
||||
DrawText(basePtr, lineStart, endPrev, chunk, handler, defaultText);
|
||||
|
||||
if (!properBreak)
|
||||
{
|
||||
if (properBreak)
|
||||
text = endPrevLine;
|
||||
|
||||
// skip a space at start of line
|
||||
if (*text == ' ')
|
||||
++text;
|
||||
|
||||
var newEnd = ImGuiNative.CalcWordWrapPositionA(ImGui.GetFont().Handle, ImGuiHelpers.GlobalScale, text, textEnd, widthLeft);
|
||||
if (properBreak && newEnd == endPrevLine)
|
||||
break;
|
||||
|
||||
endPrevLine = newEnd;
|
||||
if (endPrevLine == null)
|
||||
{
|
||||
ImGui.TextUnformatted("");
|
||||
ImGui.TextUnformatted("");
|
||||
break;
|
||||
}
|
||||
|
||||
Text(text, endPrevLine);
|
||||
|
||||
if (!properBreak)
|
||||
{
|
||||
properBreak = true;
|
||||
widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
}
|
||||
properBreak = true;
|
||||
widthLeft = ImGui.GetContentRegionAvail().X;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe byte* FindFirstSpace(byte* text, byte* textEnd)
|
||||
private static unsafe int CalcWordWrap(byte* basePtr, int start, int end, float width)
|
||||
{
|
||||
for (var i = text; i < textEnd; i++)
|
||||
if (char.IsWhiteSpace((char) *i))
|
||||
var result = ImGuiNative.CalcWordWrapPositionA(
|
||||
ImGui.GetFont().Handle,
|
||||
ImGuiHelpers.GlobalScale,
|
||||
basePtr + start,
|
||||
basePtr + end,
|
||||
width);
|
||||
if (result == null)
|
||||
return -1;
|
||||
return (int)(result - basePtr);
|
||||
}
|
||||
|
||||
private static unsafe void DrawText(byte* basePtr, int start, int end, Chunk chunk, PayloadHandler? handler, Vector4 defaultText)
|
||||
{
|
||||
var oldPos = ImGui.GetCursorScreenPos();
|
||||
|
||||
ImGuiNative.TextUnformatted(basePtr + start, basePtr + end);
|
||||
PostPayload(chunk, handler);
|
||||
|
||||
if (!ReferenceEquals(LastLink, chunk.Link))
|
||||
PayloadBounds.Clear();
|
||||
|
||||
LastLink = chunk.Link;
|
||||
|
||||
if (Hovered != null && ReferenceEquals(Hovered, chunk.Link))
|
||||
{
|
||||
defaultText.W = 0.25f;
|
||||
var actualCol = ColourUtil.Vector4ToAbgr(defaultText);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(oldPos, oldPos + ImGui.GetItemRectSize(), actualCol);
|
||||
|
||||
foreach (var (boundsStart, boundsSize) in PayloadBounds)
|
||||
ImGui.GetWindowDrawList().AddRectFilled(boundsStart, boundsStart + boundsSize, actualCol);
|
||||
|
||||
PayloadBounds.Clear();
|
||||
}
|
||||
|
||||
if (Hovered == null && chunk.Link != null)
|
||||
PayloadBounds.Add((oldPos, ImGui.GetItemRectSize()));
|
||||
}
|
||||
|
||||
private static int FindFirstSpace(ReadOnlySpan<byte> bytes, int start, int end)
|
||||
{
|
||||
for (var i = start; i < end; i++)
|
||||
if (char.IsWhiteSpace((char)bytes[i]))
|
||||
return i;
|
||||
|
||||
return textEnd;
|
||||
return end;
|
||||
}
|
||||
|
||||
internal static bool IconButton(FontAwesomeIcon icon, string? id = null, string? tooltip = null, int width = 0)
|
||||
|
||||
@@ -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)
|
||||
- **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
|
||||
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://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.
|
||||
|
||||
@@ -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 |
|
||||
| UI | Dear ImGui (Dalamud-Bindings) |
|
||||
| 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) |
|
||||
| Toolchain | dotnet 10 SDK, VS Code mit C# Dev Kit |
|
||||
| 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.
|
||||
- **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.
|
||||
- **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
|
||||
|
||||
@@ -102,7 +103,7 @@ ChatTwo/
|
||||
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
---
|
||||
@@ -181,7 +182,7 @@ Spiel starten, Hellion Chat aktivieren, Verlauf ist zurück.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
**Version 0.3.1** | Stand: Mai 2026
|
||||
**Version 0.5.4** | Stand: 2026-05-03
|
||||
|
||||
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)
|
||||
|
||||
+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.
|
||||
|
||||
**Alternative:**
|
||||
- Email: `maintainer@hellion-media.de`
|
||||
- Email: `kontakt@hellion-media.de`
|
||||
- Discord: `@j.j_kazama`
|
||||
|
||||
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,
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user