585 lines
23 KiB
C#
585 lines
23 KiB
C#
#if UNITY_EDITOR
|
||
#nullable enable
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
public static class ShrinkModSdkExporter
|
||
{
|
||
private const string ExportMenuPath = "ShrinkSDK/模组/导出开发包";
|
||
private const string DefaultOutputRootRelativePath = "GeneratedModSdk";
|
||
private const string StampFileName = ".shrink-mod-sdk.json";
|
||
private const string TemplateProjectName = "SampleShrinkMod";
|
||
private const string EventBusPackageName = "com.cneicy.shrink-eventbus";
|
||
private const string ModFrameworkPackageName = "com.cneicy.shrink-mod-framework";
|
||
private const string EventBusGeneratorProjectRelativePath = "Tools~/DotNet/ShrinkEventBus.Generator/ShrinkEventBus.Generator.csproj";
|
||
private const string BundledGeneratorRelativePath = "Editor/Scaffolding/Support/ShrinkEventBus.Generator.dll.bytes";
|
||
|
||
private static readonly string[] ExportAssemblyNames =
|
||
{
|
||
"ShrinkModFramework.Runtime",
|
||
"ShrinkEventBus.Runtime",
|
||
"ShrinkEventBus.Generator",
|
||
"ShrinkCommand.Runtime",
|
||
"ShrinkCommand.Integration.EventBus",
|
||
"ShrinkCommand.Integration.Network",
|
||
"ShrinkNetwork.Runtime",
|
||
"ShrinkNetwork.Integration.EventBus",
|
||
"ShrinkDataSaver.Runtime",
|
||
"ShrinkDataSaver.Integration.EventBus",
|
||
"Assembly-CSharp",
|
||
"UniTask",
|
||
"Newtonsoft.Json",
|
||
"Kcp-CSharp",
|
||
"System.Runtime.CompilerServices.Unsafe",
|
||
"UnityEngine",
|
||
"UnityEngine.CoreModule"
|
||
};
|
||
|
||
[MenuItem(ExportMenuPath)]
|
||
public static void ExportSdk()
|
||
{
|
||
try
|
||
{
|
||
var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
||
BuildSolution(projectRoot);
|
||
var generatorAssemblyPath = BuildOrResolveEventBusGenerator(projectRoot);
|
||
|
||
var defaultOutputRoot = Path.Combine(projectRoot, DefaultOutputRootRelativePath);
|
||
Directory.CreateDirectory(defaultOutputRoot);
|
||
|
||
var outputRoot = EditorUtility.SaveFolderPanel(
|
||
"选择 Mod SDK 导出目录",
|
||
defaultOutputRoot,
|
||
"ShrinkModSdk");
|
||
|
||
if (string.IsNullOrWhiteSpace(outputRoot))
|
||
return;
|
||
|
||
outputRoot = Path.GetFullPath(outputRoot);
|
||
PrepareOutputDirectory(outputRoot);
|
||
|
||
var libsRoot = Path.Combine(outputRoot, "Libs");
|
||
var templateRoot = Path.Combine(outputRoot, "Templates", "ExternalMod", TemplateProjectName);
|
||
Directory.CreateDirectory(libsRoot);
|
||
Directory.CreateDirectory(templateRoot);
|
||
|
||
var exportedFiles = ExportLibraries(projectRoot, generatorAssemblyPath, libsRoot);
|
||
WriteSdkReadme(outputRoot, exportedFiles);
|
||
WriteSdkManifest(outputRoot, exportedFiles);
|
||
WriteExternalTemplate(templateRoot);
|
||
WriteStamp(outputRoot);
|
||
|
||
UnityEngine.Debug.Log($"[ShrinkModFramework] 已导出 Mod SDK 开发包:{outputRoot}");
|
||
EditorUtility.RevealInFinder(outputRoot);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UnityEngine.Debug.LogError($"[ShrinkModFramework] 导出 Mod SDK 开发包失败:{ex.Message}");
|
||
UnityEngine.Debug.LogException(ex);
|
||
}
|
||
}
|
||
|
||
private static void BuildSolution(string projectRoot)
|
||
{
|
||
var solutionPath = Path.Combine(projectRoot, "ShrinkSDK.sln");
|
||
if (!File.Exists(solutionPath))
|
||
{
|
||
UnityEngine.Debug.Log("[ShrinkModFramework] 未检测到 Workspace 解决方案,直接使用当前 Unity 已编译程序集导出 Mod SDK。");
|
||
return;
|
||
}
|
||
|
||
var startInfo = new ProcessStartInfo
|
||
{
|
||
FileName = "dotnet",
|
||
Arguments = "build .\\ShrinkSDK.sln /m:1 /nr:false -p:BaseIntermediateOutputPath=Temp\\CliObj\\ModSdkExport\\",
|
||
WorkingDirectory = projectRoot,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true
|
||
};
|
||
|
||
using var process = Process.Start(startInfo);
|
||
if (process == null)
|
||
throw new InvalidOperationException("无法启动 dotnet build。");
|
||
|
||
var stdout = process.StandardOutput.ReadToEnd();
|
||
var stderr = process.StandardError.ReadToEnd();
|
||
process.WaitForExit();
|
||
|
||
if (process.ExitCode != 0)
|
||
{
|
||
throw new InvalidOperationException(
|
||
"构建 ShrinkSDK.sln 失败。\n" +
|
||
stdout + "\n" +
|
||
stderr);
|
||
}
|
||
}
|
||
|
||
private static string BuildOrResolveEventBusGenerator(string projectRoot)
|
||
{
|
||
var generatorProjectPath = ResolveEventBusGeneratorProjectPath(projectRoot);
|
||
if (generatorProjectPath == null)
|
||
{
|
||
var bundledGeneratorPath = Path.Combine(ResolveModFrameworkPackagePath(projectRoot), BundledGeneratorRelativePath);
|
||
if (File.Exists(bundledGeneratorPath))
|
||
return bundledGeneratorPath;
|
||
|
||
throw new InvalidOperationException(
|
||
"未找到 ShrinkEventBus.Generator 源码或随包分发的分析器。请安装 com.cneicy.shrink-eventbus,或使用包含 ShrinkModFramework 支持资源的完整包版本。");
|
||
}
|
||
|
||
var startInfo = new ProcessStartInfo
|
||
{
|
||
FileName = "dotnet",
|
||
Arguments = "build \"" + generatorProjectPath + "\" -c Release /nr:false",
|
||
WorkingDirectory = Path.GetDirectoryName(generatorProjectPath),
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
UseShellExecute = false,
|
||
CreateNoWindow = true
|
||
};
|
||
|
||
using var process = Process.Start(startInfo);
|
||
if (process == null)
|
||
throw new InvalidOperationException("无法启动 ShrinkEventBus.Generator 构建。");
|
||
|
||
var stdout = process.StandardOutput.ReadToEnd();
|
||
var stderr = process.StandardError.ReadToEnd();
|
||
process.WaitForExit();
|
||
if (process.ExitCode != 0)
|
||
throw new InvalidOperationException("构建 ShrinkEventBus.Generator 失败。\n" + stdout + "\n" + stderr);
|
||
|
||
var generatorAssemblyPath = Path.Combine(
|
||
Path.GetDirectoryName(generatorProjectPath) ?? string.Empty,
|
||
"bin",
|
||
"Release",
|
||
"netstandard2.0",
|
||
"ShrinkEventBus.Generator.dll");
|
||
if (!File.Exists(generatorAssemblyPath))
|
||
throw new InvalidOperationException("ShrinkEventBus.Generator 构建完成但未找到输出 DLL:" + generatorAssemblyPath);
|
||
|
||
return generatorAssemblyPath;
|
||
}
|
||
|
||
private static string? ResolveEventBusGeneratorProjectPath(string projectRoot)
|
||
{
|
||
var workspaceProjectPath = Path.Combine(
|
||
projectRoot,
|
||
"Assets",
|
||
"Modules",
|
||
"ShrinkEventBus",
|
||
EventBusGeneratorProjectRelativePath);
|
||
if (File.Exists(workspaceProjectPath))
|
||
return workspaceProjectPath;
|
||
|
||
var package = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()
|
||
.FirstOrDefault(candidate => string.Equals(candidate.name, EventBusPackageName, StringComparison.Ordinal));
|
||
if (package == null)
|
||
return null;
|
||
|
||
var packageProjectPath = Path.Combine(package.resolvedPath, EventBusGeneratorProjectRelativePath);
|
||
return File.Exists(packageProjectPath) ? packageProjectPath : null;
|
||
}
|
||
|
||
private static string ResolveModFrameworkPackagePath(string projectRoot)
|
||
{
|
||
var workspacePackagePath = Path.Combine(projectRoot, "Assets", "Modules", "ShrinkModFramework");
|
||
if (File.Exists(Path.Combine(workspacePackagePath, "package.json")))
|
||
return workspacePackagePath;
|
||
|
||
var package = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages()
|
||
.FirstOrDefault(candidate => string.Equals(candidate.name, ModFrameworkPackageName, StringComparison.Ordinal));
|
||
if (package != null && Directory.Exists(package.resolvedPath))
|
||
return package.resolvedPath;
|
||
|
||
throw new InvalidOperationException("无法解析 ShrinkModFramework 包路径。");
|
||
}
|
||
|
||
private static void PrepareOutputDirectory(string outputRoot)
|
||
{
|
||
Directory.CreateDirectory(outputRoot);
|
||
var stampPath = Path.Combine(outputRoot, StampFileName);
|
||
if (Directory.EnumerateFileSystemEntries(outputRoot).Any() && !File.Exists(stampPath))
|
||
{
|
||
throw new InvalidOperationException(
|
||
$"目标目录已存在且不是 ShrinkModFramework 自动导出的 Mod SDK 目录,为避免覆盖人工内容,已中止:{outputRoot}");
|
||
}
|
||
|
||
foreach (var entry in Directory.EnumerateFileSystemEntries(outputRoot).ToArray())
|
||
{
|
||
if (Directory.Exists(entry))
|
||
Directory.Delete(entry, true);
|
||
else
|
||
File.Delete(entry);
|
||
}
|
||
}
|
||
|
||
private static List<string> ExportLibraries(string projectRoot, string generatorAssemblyPath, string libsRoot)
|
||
{
|
||
var candidateDirectories = new[]
|
||
{
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "Assembly-CSharp"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkModFramework.Runtime"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkEventBus.Runtime"),
|
||
Path.GetDirectoryName(generatorAssemblyPath) ?? string.Empty,
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkCommand.Runtime"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkCommand.Integration.EventBus"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkCommand.Integration.Network"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkNetwork.Runtime"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkNetwork.Integration.EventBus"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkDataSaver.Runtime"),
|
||
Path.Combine(projectRoot, "Temp", "Bin", "Debug", "ShrinkDataSaver.Integration.EventBus")
|
||
};
|
||
|
||
var copied = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
var exported = new List<string>();
|
||
foreach (var assemblyName in ExportAssemblyNames)
|
||
{
|
||
foreach (var extension in new[] { ".dll", ".pdb", ".xml" })
|
||
{
|
||
var fileName = assemblyName + extension;
|
||
if (copied.Contains(fileName))
|
||
continue;
|
||
|
||
var source = candidateDirectories
|
||
.Select(directory => Path.Combine(directory, fileName))
|
||
.FirstOrDefault(File.Exists);
|
||
|
||
if (string.IsNullOrWhiteSpace(source))
|
||
continue;
|
||
|
||
var destination = Path.Combine(libsRoot, fileName);
|
||
File.Copy(source, destination, true);
|
||
copied.Add(fileName);
|
||
exported.Add(fileName.Replace('\\', '/'));
|
||
}
|
||
}
|
||
|
||
return exported.OrderBy(item => item, StringComparer.OrdinalIgnoreCase).ToList();
|
||
}
|
||
|
||
private static void WriteSdkReadme(string outputRoot, IReadOnlyList<string> exportedFiles)
|
||
{
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("# Shrink Mod SDK");
|
||
builder.AppendLine();
|
||
builder.AppendLine("这是给外部模组开发者使用的开发包。");
|
||
builder.AppendLine();
|
||
builder.AppendLine("## 内容");
|
||
builder.AppendLine();
|
||
builder.AppendLine("- `Libs/`");
|
||
builder.AppendLine(" - 已编译好的框架运行时 DLL、EventBus 代码生成器、当前游戏 DLL,以及模板需要的基础依赖");
|
||
builder.AppendLine("- `Templates/ExternalMod/SampleShrinkMod/`");
|
||
builder.AppendLine(" - 可直接复制一份开始写外部模组的模板工程");
|
||
builder.AppendLine();
|
||
builder.AppendLine("## 推荐用法");
|
||
builder.AppendLine();
|
||
builder.AppendLine("1. 复制 `Templates/ExternalMod/SampleShrinkMod/` 到你自己的工作目录");
|
||
builder.AppendLine("2. 修改项目名、命名空间、`modId`、命令路径、消息 opcode");
|
||
builder.AppendLine("3. 优先使用模板里的 `build.cmd` 构建");
|
||
builder.AppendLine("4. 把构建出来的 DLL 投放到游戏的 `Mods` 目录");
|
||
builder.AppendLine();
|
||
builder.AppendLine("## 为什么不用直接引用仓库 csproj");
|
||
builder.AppendLine();
|
||
builder.AppendLine("- 外部开发者通常拿不到完整仓库环境");
|
||
builder.AppendLine("- 独立打开 `Sdk-style csproj` 时,有些 IDE/MSBuild 环境会碰到 workload SDK 解析问题");
|
||
builder.AppendLine("- 这个开发包的目标就是让外部开发者只依赖 `Libs/` 就能开始写模组");
|
||
builder.AppendLine();
|
||
builder.AppendLine("## 当前游戏 API");
|
||
builder.AppendLine();
|
||
builder.AppendLine("- 当前先直接导出 `Assembly-CSharp.dll` 作为游戏侧 API");
|
||
builder.AppendLine("- 长期更推荐单独抽一个稳定的 `Game.ModAPI.dll`,避免把整个游戏主程序集暴露给外部模组");
|
||
builder.AppendLine();
|
||
builder.AppendLine("## 已导出的文件");
|
||
builder.AppendLine();
|
||
foreach (var exportedFile in exportedFiles)
|
||
builder.AppendLine("- `Libs/" + exportedFile + "`");
|
||
|
||
File.WriteAllText(Path.Combine(outputRoot, "README.md"), builder.ToString(), new UTF8Encoding(false));
|
||
}
|
||
|
||
private static void WriteSdkManifest(string outputRoot, IReadOnlyList<string> exportedFiles)
|
||
{
|
||
var unityVersion = File.Exists(Path.Combine(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), "ProjectSettings", "ProjectVersion.txt"))
|
||
? File.ReadAllLines(Path.Combine(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), "ProjectSettings", "ProjectVersion.txt"))
|
||
.FirstOrDefault(line => line.StartsWith("m_EditorVersion:", StringComparison.Ordinal))?
|
||
.Split(':').Last().Trim() ?? string.Empty
|
||
: string.Empty;
|
||
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("{");
|
||
builder.AppendLine($@" ""exportedAt"": ""{DateTime.UtcNow:O}"",");
|
||
builder.AppendLine($@" ""unityVersion"": ""{unityVersion}"",");
|
||
builder.AppendLine(@" ""assemblies"": [");
|
||
for (var i = 0; i < exportedFiles.Count; i++)
|
||
{
|
||
var suffix = i == exportedFiles.Count - 1 ? string.Empty : ",";
|
||
builder.AppendLine($@" ""{exportedFiles[i]}""{suffix}");
|
||
}
|
||
|
||
builder.AppendLine(" ]");
|
||
builder.AppendLine("}");
|
||
|
||
File.WriteAllText(Path.Combine(outputRoot, "manifest.json"), builder.ToString(), new UTF8Encoding(false));
|
||
}
|
||
|
||
private static void WriteExternalTemplate(string templateRoot)
|
||
{
|
||
Directory.CreateDirectory(Path.Combine(templateRoot, "src"));
|
||
|
||
File.WriteAllText(Path.Combine(templateRoot, "README.md"), BuildTemplateReadme(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, "global.json"), BuildTemplateGlobalJson(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, "build.cmd"), BuildTemplateBuildCmd(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, "dev-shell.cmd"), BuildTemplateDevShellCmd(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, TemplateProjectName + ".csproj"), BuildTemplateCsproj(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, "src", TemplateProjectName + "Contracts.cs"), BuildTemplateContractsCs(), new UTF8Encoding(false));
|
||
File.WriteAllText(Path.Combine(templateRoot, "src", TemplateProjectName + "Mod.cs"), BuildTemplateModCs(), new UTF8Encoding(false));
|
||
}
|
||
|
||
private static string BuildTemplateReadme()
|
||
{
|
||
return @"# SampleShrinkMod
|
||
|
||
这是给外部模组开发者的 DLL 模组模板。
|
||
|
||
## 目录
|
||
|
||
- `SampleShrinkMod.csproj`
|
||
- 引用 `..\..\..\Libs\` 下的 SDK DLL
|
||
- `build.cmd`
|
||
- 推荐的构建入口,会先设置 `MSBuildEnableWorkloadResolver=false`
|
||
- `dev-shell.cmd`
|
||
- 打开一个已经设置好环境变量的命令行
|
||
- `src/`
|
||
- 示例模组代码
|
||
|
||
EventBus handler 只使用 `[ShrinkEventSubscriber]` / `[ShrinkSubscribe]`。项目已把 `Libs/ShrinkEventBus.Generator.dll` 作为 Roslyn analyzer 引入,因此 Attach/Post 路径不依赖运行时反射。
|
||
|
||
## 推荐构建方式
|
||
|
||
双击或在命令行运行:
|
||
|
||
```cmd
|
||
build.cmd
|
||
```
|
||
|
||
## 如果你想开 IDE
|
||
|
||
先运行:
|
||
|
||
```cmd
|
||
dev-shell.cmd
|
||
```
|
||
|
||
然后在这个 shell 里再启动 IDE,或者继续执行 `build.cmd`。
|
||
|
||
## 注意
|
||
|
||
- 当前模板默认引用 `..\..\..\Libs\Assembly-CSharp.dll` 作为游戏 API
|
||
- 长期更推荐游戏方单独提供一个稳定的 `Game.ModAPI.dll`
|
||
";
|
||
}
|
||
|
||
private static string BuildTemplateGlobalJson()
|
||
{
|
||
return @"{
|
||
""sdk"": {
|
||
""version"": ""9.0.312"",
|
||
""rollForward"": ""latestPatch""
|
||
}
|
||
}";
|
||
}
|
||
|
||
private static string BuildTemplateBuildCmd()
|
||
{
|
||
return @"@echo off
|
||
setlocal
|
||
set MSBuildEnableWorkloadResolver=false
|
||
dotnet build .\SampleShrinkMod.csproj -c Release /nr:false
|
||
endlocal";
|
||
}
|
||
|
||
private static string BuildTemplateDevShellCmd()
|
||
{
|
||
return @"@echo off
|
||
set MSBuildEnableWorkloadResolver=false
|
||
echo MSBuildEnableWorkloadResolver=false
|
||
echo Current dotnet:
|
||
dotnet --version
|
||
cmd /k";
|
||
}
|
||
|
||
private static string BuildTemplateCsproj()
|
||
{
|
||
var references = new[]
|
||
{
|
||
"ShrinkModFramework.Runtime",
|
||
"ShrinkEventBus.Runtime",
|
||
"ShrinkCommand.Runtime",
|
||
"ShrinkCommand.Integration.EventBus",
|
||
"ShrinkCommand.Integration.Network",
|
||
"ShrinkNetwork.Runtime",
|
||
"ShrinkNetwork.Integration.EventBus",
|
||
"ShrinkDataSaver.Runtime",
|
||
"ShrinkDataSaver.Integration.EventBus",
|
||
"Assembly-CSharp",
|
||
"UniTask",
|
||
"Newtonsoft.Json",
|
||
"Kcp-CSharp",
|
||
"System.Runtime.CompilerServices.Unsafe",
|
||
"UnityEngine",
|
||
"UnityEngine.CoreModule"
|
||
};
|
||
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("<Project Sdk=\"Microsoft.NET.Sdk\">");
|
||
builder.AppendLine(" <PropertyGroup>");
|
||
builder.AppendLine(" <TargetFramework>net471</TargetFramework>");
|
||
builder.AppendLine(" <LangVersion>9.0</LangVersion>");
|
||
builder.AppendLine(" <Nullable>enable</Nullable>");
|
||
builder.AppendLine(" <ImplicitUsings>disable</ImplicitUsings>");
|
||
builder.AppendLine(" <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>");
|
||
builder.AppendLine(" <RootNamespace>SampleShrinkMod</RootNamespace>");
|
||
builder.AppendLine(" <AssemblyName>SampleShrinkMod</AssemblyName>");
|
||
builder.AppendLine(" <GenerateAssemblyInfo>false</GenerateAssemblyInfo>");
|
||
builder.AppendLine(" <EnableDefaultCompileItems>true</EnableDefaultCompileItems>");
|
||
builder.AppendLine(" <OutputPath>artifacts\\$(Configuration)\\</OutputPath>");
|
||
builder.AppendLine(" <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>");
|
||
builder.AppendLine(" </PropertyGroup>");
|
||
builder.AppendLine();
|
||
builder.AppendLine(" <ItemGroup>");
|
||
foreach (var reference in references)
|
||
{
|
||
builder.AppendLine($" <Reference Include=\"{reference}\">");
|
||
builder.AppendLine($" <HintPath>..\\..\\..\\Libs\\{reference}.dll</HintPath>");
|
||
builder.AppendLine(" <Private>true</Private>");
|
||
builder.AppendLine(" </Reference>");
|
||
}
|
||
|
||
builder.AppendLine(" </ItemGroup>");
|
||
builder.AppendLine();
|
||
builder.AppendLine(" <ItemGroup>");
|
||
builder.AppendLine(" <Analyzer Include=\"..\\..\\..\\Libs\\ShrinkEventBus.Generator.dll\" />");
|
||
builder.AppendLine(" </ItemGroup>");
|
||
builder.AppendLine("</Project>");
|
||
return builder.ToString();
|
||
}
|
||
|
||
private static string BuildTemplateContractsCs()
|
||
{
|
||
return @"#nullable enable
|
||
|
||
using ShrinkEventBus;
|
||
using ShrinkNetwork;
|
||
|
||
namespace SampleShrinkMod
|
||
{
|
||
public sealed class SampleShrinkModEvent : IShrinkEvent
|
||
{
|
||
public string Message { get; set; } = string.Empty;
|
||
}
|
||
|
||
[ShrinkNetworkMessage(41001, ""sample.shrink.mod/ping"")]
|
||
public sealed class SampleShrinkModPingRequest : IShrinkNetworkRequest
|
||
{
|
||
public string Message { get; set; } = string.Empty;
|
||
}
|
||
|
||
[ShrinkNetworkMessage(41002, ""sample.shrink.mod/ping_response"")]
|
||
public sealed class SampleShrinkModPingResponse : ShrinkRpcResponseBase
|
||
{
|
||
public string Message { get; set; } = string.Empty;
|
||
public int Counter { get; set; }
|
||
}
|
||
}";
|
||
}
|
||
|
||
private static string BuildTemplateModCs()
|
||
{
|
||
return @"#nullable enable
|
||
|
||
using ShrinkCommand;
|
||
using ShrinkEventBus;
|
||
using ShrinkModFramework;
|
||
using ShrinkNetwork;
|
||
|
||
namespace SampleShrinkMod
|
||
{
|
||
[ShrinkMod(""sample.shrink.mod"", ""Sample Shrink Mod"", ""1.0.0"")]
|
||
[ShrinkEventSubscriber(OwnerId = ""sample.shrink.mod"", DefaultBus = ""mod:sample.shrink.mod"")]
|
||
[ShrinkCommandSubscriber]
|
||
[ShrinkNetworkSubscriber]
|
||
public sealed partial class SampleShrinkModMod : ShrinkModBase
|
||
{
|
||
private readonly RuntimeState _state = new();
|
||
|
||
public override void OnConstruct(ShrinkModContext context)
|
||
{
|
||
context.Log(""模组已构造。"");
|
||
}
|
||
|
||
public override void OnRegisterContent(ShrinkModContext context)
|
||
{
|
||
context.RegisterContent(""example.items"", ""example_item"", ""Sample Shrink Mod Item"");
|
||
}
|
||
|
||
public override void OnInitialize(ShrinkModContext context)
|
||
{
|
||
context.RegisterNetworkHandler<int>(""sync.counter"", (_, value) =>
|
||
{
|
||
_state.Counter = value;
|
||
context.Log(""收到模组网络计数:"" + value);
|
||
});
|
||
}
|
||
|
||
[ShrinkSubscribe]
|
||
private void OnExampleEvent(SampleShrinkModEvent evt)
|
||
{
|
||
_state.LastEventMessage = evt.Message ?? string.Empty;
|
||
_state.Counter++;
|
||
}
|
||
|
||
[ShrinkCommand(""sample_shrink_mod ping"", Description = ""测试模组命令"", Permission = ""sample.shrink.mod.command.ping"")]
|
||
private string Ping()
|
||
{
|
||
return ""pong:"" + _state.Counter;
|
||
}
|
||
|
||
[ShrinkNetworkSubscribe(Permission = ""sample.shrink.mod.rpc.ping"")]
|
||
private SampleShrinkModPingResponse HandlePing(SampleShrinkModPingRequest request)
|
||
{
|
||
return new SampleShrinkModPingResponse
|
||
{
|
||
Message = ""pong:"" + (request.Message ?? string.Empty),
|
||
Counter = _state.Counter
|
||
};
|
||
}
|
||
|
||
private sealed class RuntimeState
|
||
{
|
||
public int Counter { get; set; }
|
||
public string LastEventMessage { get; set; } = string.Empty;
|
||
}
|
||
}
|
||
}";
|
||
}
|
||
|
||
private static void WriteStamp(string outputRoot)
|
||
{
|
||
var builder = new StringBuilder();
|
||
builder.AppendLine("{");
|
||
builder.AppendLine(@" ""generatedBy"": ""ShrinkModSdkExporter"",");
|
||
builder.AppendLine($@" ""generatedAt"": ""{DateTime.UtcNow:O}""");
|
||
builder.AppendLine("}");
|
||
File.WriteAllText(Path.Combine(outputRoot, StampFileName), builder.ToString(), new UTF8Encoding(false));
|
||
}
|
||
}
|
||
#endif
|