commit 4a2e8408886ba8c4a4e59028d1a16c57d7788670 Author: Florian Wathling Date: Sat May 9 16:41:15 2026 +0200 Initial template setup 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(); + } + } +}