Add LabAPI plugin dotnet new template

This commit is contained in:
2026-08-26 23:03:22 +02:00
commit 6f7cff954a
8 changed files with 369 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
root = true
[*]
charset = utf-8
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
[*.cs]
indent_style = space
indent_size = 4
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true:suggestion
# Naming and language conventions here reflect this project's own style; they
# do not by themselves enforce the "no unsolicited comments" / "English by
# default" rules in AGENTS.md / CLAUDE.md, which apply to any human or AI
# contributor regardless of what an analyzer can mechanically check.
[*.{csproj,json,yml,yaml}]
indent_style = space
indent_size = 2
+4
View File
@@ -0,0 +1,4 @@
bin/
obj/
*.user
.vs/
+50
View File
@@ -0,0 +1,50 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Your name / organization",
"classifications": [ "Common", "Library", "SCP:SL", "LabAPI", "Game Plugin" ],
"identity": "SCPSL.LabApi.PluginTemplate",
"name": "SCP: Secret Laboratory LabAPI Plugin",
"shortName": "labapi-plugin",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "LabApiPluginTemplate",
"preferNameDirectory": true,
"symbols": {
"LabApiVersion": {
"type": "parameter",
"datatype": "string",
"defaultValue": "1.1.7",
"replaces": "1.1.7",
"description": "Version of the Northwood.LabAPI NuGet package to install in the project."
},
"PluginAuthor": {
"type": "parameter",
"datatype": "string",
"defaultValue": "YourName",
"replaces": "YourName",
"description": "Name used as the plugin's author (EntryPoint's Author property)."
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If set, skips running 'dotnet restore' automatically after creating the project.",
"defaultValue": "false"
}
},
"primaryOutputs": [
{ "path": "LabApiPluginTemplate.csproj" }
],
"postActions": [
{
"condition": "(!skipRestore)",
"description": "Restores the NuGet packages required by this project (including LabAPI).",
"manualInstructions": [
{ "text": "Run 'dotnet restore'" }
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}
+69
View File
@@ -0,0 +1,69 @@
# AGENTS.md
This repo is the source of a `dotnet new` template (shortName `labapi-plugin`) that
scaffolds SCP: Secret Laboratory LabAPI plugins. This file follows the
editor/vendor-agnostic [agents.md](https://agents.md) convention for AI coding tools
other than Claude Code (Cursor, Codex, etc.). **`CLAUDE.md` is this repo's primary,
detailed instructions file** — read it first if your tooling can. What follows is the
condensed subset any agent needs, kept in sync with `CLAUDE.md`.
## This is template source, not a plugin
This section applies only while `.template.config/` is present next to this file —
i.e. you're editing the template repo itself, not a plugin already generated from it.
`.template.config/template.json` makes the dotnet templating engine do a **literal,
repo-wide string replacement** at generation time (not semantic renaming): this
folder's own name (`sourceName`) becomes whatever project name a consumer picks, the
placeholder author name in `EntryPoint.cs` becomes their `--PluginAuthor`, and the
LabAPI version pinned in the `.csproj`/`EntryPoint.cs` becomes their
`--LabApiVersion`. See `template.json` for the exact current literals — don't repeat
them verbatim in prose here, since this file ships inside every generated project and
an exact match would get silently rewritten too. Don't introduce an exact match of
one of those literals elsewhere in the repo unless you mean for it to be substituted;
add a new `template.json` symbol instead of hardcoding a magic string. `EntryPoint`/
`Config` (the class names) are intentionally **not** substituted — only the namespace
tracks the project name.
## Never invent LabAPI API surface
Don't assume a LabAPI class/namespace/method/property/attribute exists. Verify it
against the restored `Northwood.LabAPI` package (`LabApi.dll`/`LabApi.xml` under
`%userprofile%\.nuget\packages\northwood.labapi\<version>\lib\net48\`) or
https://github.com/northwood-studios/LabAPI first. If you can't verify a member,
say so instead of guessing.
## Toolchain choices — don't change without being asked
`TargetFramework` is `netstandard2.1` on purpose (LabAPI itself only ships
`net48`/`net481`), chosen so the plugin still loads under the game's Mono/.NET
Framework 4.8 host. `LangVersion` is pinned to `14.0`. The `.csproj` suppresses
exactly `NU1701` (scoped to the `Northwood.LabAPI` `PackageReference`) and `MSB3270`
(via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`) because of that
netstandard2.1/net48-only-package combination — don't broaden either into a blanket
suppression or remove them.
## Code style and language
No `//`, `/* */`, or `///` comments unless the user explicitly asks for them in that
request; don't add comments restating self-explanatory code — `EntryPoint.cs`/
`Config.cs` are already comment-free, follow the same standard for new files. All
text embedded in code (log/console messages, exceptions, config descriptions,
user-facing strings) defaults to English — e.g. `Logger.Info("Plugin initialized
successfully")` — except identifiers the user requested (project name, namespace,
class/method/property/variable names), which are never translated, and text the user
explicitly asks to be in another language.
## Verifying a change
No automated tests exist. Verify by generating a real project and building it:
```bash
dotnet new uninstall . && dotnet new install .
dotnet new labapi-plugin -n VerifyPlugin -o ../_verify/VerifyPlugin
cd ../_verify/VerifyPlugin && dotnet build
```
Expect `Build succeeded. 0 Warning(s) 0 Error(s)`. See `CLAUDE.md` for the full
verification notes (cache invalidation after editing `template.json`, Windows cleanup
caveats).
+142
View File
@@ -0,0 +1,142 @@
# CLAUDE.md
This repo **is** a `dotnet new` project template (identity `SCPSL.LabApi.PluginTemplate`,
shortName `labapi-plugin`), not a runnable plugin. It scaffolds SCP: Secret Laboratory
LabAPI plugins. `AGENTS.md` carries a shorter, tool-agnostic version of the essentials
for non-Claude agents; this file is the authoritative one — keep both in sync when a
rule below changes.
The next section only applies while you're editing **this template repository**
(i.e. a `.template.config/` folder is present next to this file). It does not apply
if you're instead looking at a plugin someone already generated from this template —
that folder never contains `.template.config/`, and its `EntryPoint.cs`/`Config.cs`
are ordinary project files at that point, not template scaffolding.
## Repo is template *source* — read before touching any file
`.template.config/template.json` sets this folder's name as the template's
`sourceName` and defines two text-replacement `symbols` (plus a third, `skipRestore`,
that only toggles the post-generation `dotnet restore` and replaces no text). The
dotnet templating engine does a literal, repo-wide **string replacement** at
generation time — not semantic renaming — driven by that config:
- The folder/project name (`sourceName` in `template.json`, currently this repo's own
folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`.
- The placeholder value of the `PluginAuthor` symbol (the string currently sitting in
`EntryPoint.cs`'s `Author` property) → the consumer's `--PluginAuthor`.
- The placeholder value of the `LabApiVersion` symbol (the version string currently in
the `.csproj`'s `PackageReference` and in `EntryPoint.cs`'s `RequiredApiVersion`) →
the consumer's `--LabApiVersion`.
Open `template.json` to see the exact current literal for each — don't hardcode them
again here or anywhere else in prose, because this file itself ships inside every
generated project and any exact match gets rewritten too (self-referential text like
"the project is called X" would silently turn into nonsense after generation).
Consequences:
- Never introduce an exact match of one of those three literals elsewhere in the repo
unless you intend it to be substituted too.
- If you add a new thing that should be configurable at generation time, add a symbol
to `template.json` (`type: parameter`, `replaces: "<literal>"`) instead of
hardcoding a magic string.
- `EntryPoint` and `Config` (the class names) are **not** substituted and must stay
literally named that — LabAPI's `PluginLoader` discovers plugins by type
inheritance, not by name, and the file names `EntryPoint.cs`/`Config.cs` are a fixed
part of this template's contract. Only the *namespace* tracks the project name.
## Repo layout (only what affects decisions)
- `.template.config/template.json` — template identity, the 3 symbols above,
`primaryOutputs` (points at the project file using its pre-substitution literal
name — that's correct and expected, not a bug), and a `postActions` restore step
(the standard dotnet-templating "restore NuGet packages" action, id
`210D431B-...`; skipped in Visual Studio, which restores on its own).
- The `.csproj` at the repo root — the project file every generated plugin ships with.
- `EntryPoint.cs` / `Config.cs` — the scaffolded plugin (`Plugin<Config>`) and its
YAML-backed settings class.
- `.editorconfig` — formatting/naming only (indentation, brace style). It does **not**
enforce the comment/language rules below — those aren't analyzer-checkable.
- No `.sln`, no test project, no CI, no packaging (`.nupkg`) project, no git repo, and
no GitHub Copilot instructions file currently exist in this folder. Don't assume
any of them.
## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed)
`Plugin<TConfig>` (`LabApi.Loader.Features.Plugins`) requires implementing:
`Name`, `Description`, `Author`, `RequiredApiVersion` (all abstract get-only) plus
`Enable()`/`Disable()`. Overridable but optional: `Version` (defaults to the
assembly's version), `Priority` (`LoadPriority`, defaults to `Medium`),
`ConfigFileName` (defaults to `"config.yml"`). `Config` is a plain `class` with a
public parameterless constructor and public get/set properties — the base class
(de)serializes it via YamlDotNet automatically; don't hand-roll config I/O.
**Never invent a LabAPI class/namespace/method/property/attribute.** Before using one
you haven't already seen in this repo, check the restored package (`LabApi.dll` +
`LabApi.xml` under `%userprofile%\.nuget\packages\northwood.labapi\<version>\lib\net48\`)
or https://github.com/northwood-studios/LabAPI. If you can't verify a member exists,
say so instead of guessing. No Skill/plugin specific to C#, .NET, MSBuild, NuGet, or
LabAPI exists in the Claude Code skill catalog as of this writing — check
`ToolSearch`/`SearchSkills` for one before editing in case that has changed, but don't
assume one exists.
## Toolchain choices — don't "fix" these without being asked
- The project's `TargetFramework` is `netstandard2.1` on purpose, even though
LabAPI's own docs and the `Northwood.LabAPI` NuGet package only target
`net48`/`net481`. This is a deliberate compatibility choice (compiled output still
loads under the game's Mono/.NET Framework 4.8 host) that trades away some
netstandard2.1 BCL/language surface .NET Framework can't actually run (e.g. default
interface members). Don't switch it to `net48`, don't add multi-targeting, unless
the user asks.
- `LangVersion` is pinned explicitly (currently `14.0`) — don't let it drift to
`latest`.
- The `.csproj` suppresses exactly two warnings, both scoped as narrowly as MSBuild
allows, because of the netstandard2.1/net48-only-package combination above:
`NU1701` (via a per-`PackageReference` `<NoWarn>`, not a project-wide one) and
`MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't
broaden either into a blanket suppression and don't remove them without
understanding why (comments sit next to both in the `.csproj`).
## Code style for anything you write here
- No `//`, `/* */`, or `///` comments unless the user explicitly asks for
comments/documentation in that request. No comments restating self-explanatory code.
`EntryPoint.cs`/`Config.cs` are already comment-free — keep them that way and follow
the same standard for any new file.
- All text embedded in code defaults to **English**: string/log/console messages,
exception messages, `[Description]`/config text, any user-facing string. Example:
`Logger.Info("Plugin initialized successfully")`, not a Spanish equivalent.
- This never applies to identifiers the user picked — project name, namespace,
class/method/property/variable names stay exactly as requested (standard C# casing
rules aside). Honor an explicit request for a specific piece of text in another
language.
## How to verify a change (no test suite exists — this is the actual check)
There is no automated test project. Verify any template change by actually
generating a project from it and building that output. Replace `<repo-name>` below
with this repo's actual folder name (the value you'd see in `sourceName`):
```bash
dotnet new uninstall .
dotnet new install .
dotnet new labapi-plugin -n VerifyPlugin -o ../_verify/VerifyPlugin
cd ../_verify/VerifyPlugin
dotnet build
```
Expect `Build succeeded. 0 Warning(s) 0 Error(s)`. If it isn't 0/0, the change broke
either the template's own warning suppressions or something in `EntryPoint.cs`/
`Config.cs`/the `.csproj`.
Notes:
- Re-run `dotnet new uninstall .` + `dotnet new install .` after **any** edit to
`.template.config/template.json` or the scaffolded files — the template cache
keys off the installed path and stale content otherwise gets regenerated.
- `dotnet build` also works directly inside `<repo-name>` (the placeholder project
is itself valid and compilable), but that only proves the *unsubstituted*
placeholder compiles — it does not exercise symbol substitution. Always additionally
verify through an actual `dotnet new` generation before calling a change done.
- On Windows, if cleaning up `../_verify` fails with "resource busy", run
`dotnet build-server shutdown` first, then retry.
- Delete the `../_verify` scratch output when done; it isn't part of the template.
+10
View File
@@ -0,0 +1,10 @@
using System.ComponentModel;
namespace LabApiPluginTemplate
{
public class Config
{
[Description("Whether the plugin is enabled.")]
public bool IsEnabled { get; set; } = true;
}
}
+30
View File
@@ -0,0 +1,30 @@
using System;
using LabApi.Features.Console;
using LabApi.Loader.Features.Plugins;
using LabApi.Loader.Features.Plugins.Enums;
namespace LabApiPluginTemplate
{
public class EntryPoint : Plugin<Config>
{
public override string Name => "LabApiPluginTemplate";
public override string Description => "TODO: describe what this plugin does.";
public override string Author => "YourName";
public override Version RequiredApiVersion { get; } = new Version("1.1.7");
public override LoadPriority Priority => LoadPriority.Medium;
public override void Enable()
{
Logger.Info($"{Name} has been enabled.");
}
public override void Disable()
{
Logger.Info($"{Name} has been disabled.");
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- .NET Standard 2.1: the most modern ABI that can still be loaded inside the
.NET Framework 4.8 / Mono process the SCP:SL dedicated server uses, letting the
project use recent C# syntax (see LangVersion) while still producing an
assembly compatible with the game's host runtime. -->
<TargetFramework>netstandard2.1</TargetFramework>
<!-- C# language version explicitly requested by the project. -->
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<RootNamespace>LabApiPluginTemplate</RootNamespace>
<AssemblyName>LabApiPluginTemplate</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- Northwood.LabAPI only ships net48/net481 binaries. Restoring it into a
netstandard2.1 project makes NuGet fall back to that asset group and raise
NU1701 to flag it. That's expected and safe here (the resulting assembly is
still loaded by the server's .NET Framework/Mono runtime either way), so this
suppresses just that one warning and only for this package. -->
<NoWarn>$(NoWarn);NU1701</NoWarn>
<!-- LabAPI's prebuilt assembly (and the game's own managed assemblies it depends
on) are built for .NET Framework, which on some SDK/platform combinations
triggers a processor-architecture mismatch warning (MSB3270) when resolving
references. It isn't an actual runtime problem, so this turns off just that
specific check instead of every MSBuild warning. -->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Northwood.LabAPI" Version="1.1.7">
<NoWarn>NU1701</NoWarn>
</PackageReference>
</ItemGroup>
</Project>