From 4a2e8408886ba8c4a4e59028d1a16c57d7788670 Mon Sep 17 00:00:00 2001 From: Florian Wathling Date: Sat, 9 May 2026 16:41:15 +0200 Subject: [PATCH] Initial template setup --- .editorconfig | 30 +++++++ .gitea/ISSUE_TEMPLATE/bug_report.md | 26 ++++++ .gitea/ISSUE_TEMPLATE/feature_request.md | 22 +++++ .gitea/PULL_REQUEST_TEMPLATE.md | 22 +++++ .gitea/forge-posts/v0.1.0.md | 16 ++++ .gitea/workflows/ci.yml | 27 ++++++ .gitea/workflows/forge-auto-announce.yml | 75 +++++++++++++++++ .gitignore | 34 ++++++++ CHANGELOG.md | 7 ++ CODEOWNERS | 8 ++ LICENSE | 21 +++++ PluginNameTemplate.csproj | 31 +++++++ PluginNameTemplate.yaml | 30 +++++++ README.md | 103 +++++++++++++++++++++++ docs/CHANGELOG.md | 7 ++ images/.gitkeep | 3 + repo.json | 20 +++++ src/Plugin.cs | 28 ++++++ src/PluginConfiguration.cs | 23 +++++ src/Windows/ConfigWindow.cs | 28 ++++++ 20 files changed, 561 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitea/ISSUE_TEMPLATE/bug_report.md create mode 100644 .gitea/ISSUE_TEMPLATE/feature_request.md create mode 100644 .gitea/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitea/forge-posts/v0.1.0.md create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/forge-auto-announce.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODEOWNERS create mode 100644 LICENSE create mode 100644 PluginNameTemplate.csproj create mode 100644 PluginNameTemplate.yaml create mode 100644 README.md create mode 100644 docs/CHANGELOG.md create mode 100644 images/.gitkeep create mode 100644 repo.json create mode 100644 src/Plugin.cs create mode 100644 src/PluginConfiguration.cs create mode 100644 src/Windows/ConfigWindow.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..15a8c69 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{json,yaml,yml,md}] +indent_size = 2 + +[*.{csproj,xml}] +indent_size = 2 + +[*.cs] +# Namespace conventions +csharp_style_namespace_declarations = file_scoped:warning +# Newline preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +# Var preferences +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +# Expression-bodied members +csharp_style_expression_bodied_methods = when_on_single_line:silent +csharp_style_expression_bodied_properties = true:suggestion diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..031c2d2 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug Report +about: Something is broken or behaves unexpectedly +title: "[Bug] " +labels: ["bug"] +--- + +## What happened + + + +## Steps to reproduce + +1. +2. +3. + +## Environment + +- Version: +- OS: +- Anything else relevant: + +## Logs / Screenshots + + diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..693fd4d --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature Request +about: Suggest an idea or improvement +title: "[Feature] " +labels: ["enhancement"] +--- + +## The problem + + + +## Proposed solution + + + +## Alternatives considered + + + +## Additional context + + diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d00988a --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +## Summary + + + +- + +## Why + + + +## Testing + + + +- [ ] + +## Checklist + +- [ ] Code builds without warnings +- [ ] Tests pass (or N/A) +- [ ] Documentation updated (or N/A) +- [ ] No secrets or credentials committed diff --git a/.gitea/forge-posts/v0.1.0.md b/.gitea/forge-posts/v0.1.0.md new file mode 100644 index 0000000..db65382 --- /dev/null +++ b/.gitea/forge-posts/v0.1.0.md @@ -0,0 +1,16 @@ +--- +title: "PluginNameTemplate v0.1.0" +subtitle: "Initial release — short one-liner about what this version brings" +versionsnatur: "Initial release" +--- + +First public release on the Hellion Forge. + +**Highlights** + +- Bullet 1 +- Bullet 2 + +**Notes** + +Anything testers should know — known issues, breaking changes from the previous version, etc. Keep it tight; the embed has a ~5500 char total cap. diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..812b4eb --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restore + run: dotnet restore + + - name: Build (Release) + run: dotnet build -c Release --no-restore + + - name: Verify Dalamud manifest exists + run: test -f bin/Release/PluginNameTemplate/PluginNameTemplate.json diff --git a/.gitea/workflows/forge-auto-announce.yml b/.gitea/workflows/forge-auto-announce.yml new file mode 100644 index 0000000..afd3266 --- /dev/null +++ b/.gitea/workflows/forge-auto-announce.yml @@ -0,0 +1,75 @@ +# Forge-Auto-Announce +# Triggers on tag push (v*). Reads .gitea/forge-posts/v.md from the +# *tagged tree* (not main), assembles a Discord embed, posts via webhook. +# +# Required repo secret: FORGE_DISCORD_WEBHOOK_URL +# Optional repo secret: FORGE_DISCORD_THREAD_ID (post into a forum thread) +# +# Char-cap reminder: Discord embed total (title + description + footer + +# field values combined) ~5500. Keep .gitea/forge-posts/v*.md trim. + +name: Forge Auto-Announce + +on: + push: + tags: + - 'v*' + +jobs: + announce: + runs-on: ubuntu-latest + steps: + - name: Checkout tagged tree + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Compose embed + id: embed + env: + TAG: ${{ github.ref_name }} + run: | + POST_FILE=".gitea/forge-posts/${TAG}.md" + if [ ! -f "$POST_FILE" ]; then + echo "No forge-post file for ${TAG} (looked at ${POST_FILE}). Skipping." + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + TITLE=$(awk -F': ' '/^title:/ {print $2; exit}' "$POST_FILE" | sed 's/^"//;s/"$//') + SUBTITLE=$(awk -F': ' '/^subtitle:/ {print $2; exit}' "$POST_FILE" | sed 's/^"//;s/"$//') + BODY=$(awk '/^---$/{c++;next} c==2' "$POST_FILE") + + jq -n \ + --arg title "${TITLE:-Release ${TAG}}" \ + --arg subtitle "${SUBTITLE}" \ + --arg body "$BODY" \ + --arg tag "$TAG" \ + --arg repo "${{ github.repository }}" \ + '{ + embeds: [{ + title: $title, + description: ($subtitle + "\n\n" + $body), + color: 12731404, + url: ("https://gitea.hellion-forge.cloud/" + $repo + "/releases/tag/" + $tag), + footer: { text: ($repo + " — " + $tag) } + }] + }' > payload.json + + echo "skip=false" >> "$GITHUB_OUTPUT" + + - name: Post to Discord + if: steps.embed.outputs.skip != 'true' + env: + WEBHOOK: ${{ secrets.FORGE_DISCORD_WEBHOOK_URL }} + THREAD_ID: ${{ secrets.FORGE_DISCORD_THREAD_ID }} + run: | + if [ -z "$WEBHOOK" ]; then + echo "::error::FORGE_DISCORD_WEBHOOK_URL secret missing" + exit 1 + fi + URL="$WEBHOOK" + if [ -n "$THREAD_ID" ]; then + URL="${URL}?thread_id=${THREAD_ID}" + fi + curl -fsS -X POST -H 'Content-Type: application/json' --data @payload.json "$URL" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19a48a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Build output +bin/ +obj/ +out/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# Visual Studio +.vs/ +*.dotsettings.user + +# Rider +.idea/ + +# Test artifacts +[Tt]estResults/ +*.coverage +*.coveragexml + +# Dalamud packaging +*.pdb +latest.zip + +# Local secrets +*.local.json +appsettings.local.json +.env +.env.local + +# OS +.DS_Store +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..13fcdcc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +Slim copy — only the latest 2-4 versions live here. Older entries move to `docs/CHANGELOG.md`. + +## v0.1.0 + +- Initial release. diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e54a1af --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,8 @@ +# CODEOWNERS — automatic review-assignment for PRs. +# Syntax: +# +# More: https://docs.gitea.com/usage/code-owners +# +# Default owner for everything in the repo. +# Replace with the appropriate user/team for the new repo. +* @JonKazama-Hellion diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..765ff79 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Florian Wathling / Hellion Online Media + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PluginNameTemplate.csproj b/PluginNameTemplate.csproj new file mode 100644 index 0000000..3d28fdd --- /dev/null +++ b/PluginNameTemplate.csproj @@ -0,0 +1,31 @@ + + + + + 0.1.0 + 0.1.0 + 0.1.0 + Short one-line description of what your plugin does. + Florian Wathling / Hellion Online Media + Hellion Online Media + Copyright (c) 2026 Florian Wathling / Hellion Online Media + + PluginNameTemplate + PluginNameTemplate + + + + + + + + + + diff --git a/PluginNameTemplate.yaml b/PluginNameTemplate.yaml new file mode 100644 index 0000000..1d2ed45 --- /dev/null +++ b/PluginNameTemplate.yaml @@ -0,0 +1,30 @@ +# Dalamud manifest. Used by the official plugin repo and by custom repos +# (see repo.json). Keep in sync with .csproj and repo.json on every version bump. + +name: PluginNameTemplate +author: Florian Wathling +punchline: Short tag-line that shows in the plugin installer. +description: | + Longer description of what your plugin does. Markdown is supported in some + Dalamud versions; safest to keep it plain text. Multi-line is fine. + +repo_url: https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate +icon_url: https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate/raw/branch/main/images/icon.png + +assembly_version: 0.1.0 +testing_assembly_version: 0.1.0 + +dalamud_api_level: 13 + +categories: + - utility + +tags: + - hellion-forge + +changelog: | + v0.1.0 + - Initial release. + +# Don't override DalamudPackager defaults (Dalamud.NET.Sdk 13+ handles icon and +# image_urls automatically via the .csproj ). diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e961ef --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# Dalamud Plugin Template + +A starting point for FFXIV/Dalamud plugins on the [Hellion Forge](https://gitea.hellion-forge.cloud/). + +Distilled from the [HellionChat](https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat) plugin patterns: csproj layout, configuration handling, window scaffolding, custom-repo manifest, Forge-Auto-Announce workflow, and the version-bump checklist. + +--- + +## How to use this template + +1. Click **"Use this template"** at the top of the repository page on the Forge. +2. Pick a name like `MyPlugin` and clone your new repo locally. +3. Find-and-replace `PluginNameTemplate` everywhere with your plugin's name (case-sensitive). + ```bash + git ls-files | xargs sed -i 's/PluginNameTemplate/MyPlugin/g' + git mv PluginNameTemplate.csproj MyPlugin.csproj + git mv PluginNameTemplate.yaml MyPlugin.yaml + ``` +4. Replace `images/icon.png` with your plugin icon (512x512 PNG, transparent background). +5. Update `repo.json` with your real `DownloadLinkInstall` URLs once your CI publishes releases. +6. Implement your plugin in `src/Plugin.cs` and friends. + +After the rename, this README should be replaced with your plugin's actual README — the template-usage notes don't belong in your shipped plugin. + +--- + +## Project structure + +``` +. +├── .editorconfig Code style +├── .gitea/ +│ ├── ISSUE_TEMPLATE/ Standard issue forms +│ ├── PULL_REQUEST_TEMPLATE.md +│ └── workflows/ +│ ├── ci.yml Build verification on push/PR +│ └── forge-auto-announce.yml Discord announcement on tag +├── docs/CHANGELOG.md Long-form changelog (slim main copy) +├── images/icon.png Plugin icon (replace before shipping) +├── src/ +│ ├── Plugin.cs IDalamudPlugin entry point +│ ├── PluginConfiguration.cs IPluginConfiguration +│ └── Windows/ +│ └── ConfigWindow.cs Skeleton config window +├── PluginNameTemplate.csproj Dalamud-SDK csproj +├── PluginNameTemplate.yaml Dalamud manifest +├── repo.json Custom-repo manifest for testers +├── CHANGELOG.md Slim changelog (latest 2-4 versions) +├── CODEOWNERS Default reviewer +├── LICENSE MIT +└── README.md This file (replace before shipping) +``` + +--- + +## Build + +```bash +dotnet restore +dotnet build -c Release +``` + +DalamudPackager produces the `.zip` artifact under `bin/Release//latest.zip`. + +**Note:** Don't override the default `DalamudPackager.targets` from your csproj — it'll silently strip the icon and ImageUrls from your manifest. If you need custom packaging, do it via csproj properties only. + +--- + +## Versioning + +Synchronized version fields (bump all at once): + +- `.csproj` → `AssemblyVersion` +- `.yaml` → `assembly_version` + `changelog` +- `repo.json` → `AssemblyVersion`, `TestingAssemblyVersion`, all 3 `DownloadLink*` URLs, `Description`, `Changelog` +- `CHANGELOG.md` (slim) and `docs/CHANGELOG.md` (full) — keep the latest 2-4 versions in the slim copy +- `.gitea/forge-posts/v.md` — Discord announcement payload + +Sanity-check before tagging: + +```bash +grep -rn "" . | grep -v -E '(bin|obj|node_modules|.git/)' +``` + +The Forge-Auto-Announce workflow reads from the **tagged tree**, not main. If a fix-commit lands after the tag, force-push the tag. + +--- + +## Testing + +Service classes coupled to Dalamud (`IPluginInterface`, `IDataManager`, etc.) cannot be instantiated directly in xUnit because the Dalamud assembly isn't on the test AppDomain. Patterns that work: + +- **Pure helpers** — extract logic into Dalamud-free classes, test those directly +- **Constructor injection** — pass utility dependencies in, mock them in tests +- **External test repo** — keep tests in a private side-repo if they need a richer harness + +The default csproj has no test project. Add one when there's something to test. + +--- + +## License + +MIT — see `LICENSE`. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..1d27fbc --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog (full) + +The complete history. Newest entries at the top. The slim `CHANGELOG.md` in the repo root keeps the latest 2-4 versions; older ones move here. + +## v0.1.0 + +- Initial release. diff --git a/images/.gitkeep b/images/.gitkeep new file mode 100644 index 0000000..298d840 --- /dev/null +++ b/images/.gitkeep @@ -0,0 +1,3 @@ +Replace this file with `icon.png` (512x512 PNG, transparent background) before shipping. + +The plugin manifest (yaml + repo.json) references `images/icon.png` directly. diff --git a/repo.json b/repo.json new file mode 100644 index 0000000..0741740 --- /dev/null +++ b/repo.json @@ -0,0 +1,20 @@ +[ + { + "Author": "Florian Wathling", + "Name": "PluginNameTemplate", + "Punchline": "Short tag-line that shows in the plugin installer.", + "Description": "Longer description for testers using the custom repo.", + "InternalName": "PluginNameTemplate", + "AssemblyVersion": "0.1.0", + "TestingAssemblyVersion": "0.1.0", + "RepoUrl": "https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate", + "ApplicableVersion": "any", + "Tags": ["hellion-forge"], + "DalamudApiLevel": 13, + "IconUrl": "https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate/raw/branch/main/images/icon.png", + "DownloadLinkInstall": "https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate/releases/download/v0.1.0/latest.zip", + "DownloadLinkUpdate": "https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate/releases/download/v0.1.0/latest.zip", + "DownloadLinkTesting": "https://gitea.hellion-forge.cloud/Hellion-Forge/PluginNameTemplate/releases/download/v0.1.0/latest.zip", + "Changelog": "v0.1.0 — Initial release." + } +] diff --git a/src/Plugin.cs b/src/Plugin.cs new file mode 100644 index 0000000..38c39fc --- /dev/null +++ b/src/Plugin.cs @@ -0,0 +1,28 @@ +using Dalamud.IoC; +using Dalamud.Plugin; +using Dalamud.Plugin.Services; + +namespace PluginNameTemplate; + +public sealed class Plugin : IDalamudPlugin +{ + [PluginService] public static IDalamudPluginInterface Pi { get; private set; } = null!; + [PluginService] public static IPluginLog Log { get; private set; } = null!; + [PluginService] public static ICommandManager Commands { get; private set; } = null!; + + private readonly PluginConfiguration config; + + public Plugin() + { + this.config = PluginConfiguration.Load(); + + // Register your commands, hooks, windows, etc. here. + Log.Information("PluginNameTemplate loaded."); + } + + public void Dispose() + { + // Unregister anything that was registered above. Order matters — + // dispose UI before hooks, hooks before services. + } +} diff --git a/src/PluginConfiguration.cs b/src/PluginConfiguration.cs new file mode 100644 index 0000000..a71c77f --- /dev/null +++ b/src/PluginConfiguration.cs @@ -0,0 +1,23 @@ +using Dalamud.Configuration; + +namespace PluginNameTemplate; + +public sealed class PluginConfiguration : IPluginConfiguration +{ + public int Version { get; set; } = 1; + + // Add your config fields below. Plain types serialize cleanly; complex + // types need [JsonConverter] or a manual migration step. + + public bool ExampleToggle { get; set; } = false; + + public static PluginConfiguration Load() + { + return Plugin.Pi.GetPluginConfig() as PluginConfiguration ?? new PluginConfiguration(); + } + + public void Save() + { + Plugin.Pi.SavePluginConfig(this); + } +} diff --git a/src/Windows/ConfigWindow.cs b/src/Windows/ConfigWindow.cs new file mode 100644 index 0000000..2888d07 --- /dev/null +++ b/src/Windows/ConfigWindow.cs @@ -0,0 +1,28 @@ +using System.Numerics; +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace PluginNameTemplate.Windows; + +public sealed class ConfigWindow : Window +{ + private readonly PluginConfiguration config; + + public ConfigWindow(PluginConfiguration config) + : base("PluginNameTemplate Settings###PluginNameTemplate-config") + { + this.config = config; + this.Size = new Vector2(420, 320); + this.SizeCondition = ImGuiCond.FirstUseEver; + } + + public override void Draw() + { + var toggle = this.config.ExampleToggle; + if (ImGui.Checkbox("Example toggle", ref toggle)) + { + this.config.ExampleToggle = toggle; + this.config.Save(); + } + } +}