|
| 1 | +这部分很麻烦。 |
| 2 | + |
| 3 | +本人不建议任何新手在未学习完全部用法且熟练掌握的前提下尝试自定义标签和检测。 |
| 4 | + |
| 5 | +而且常有一些十分诡异的bug出现,甚至导致游戏崩溃。 |
| 6 | + |
| 7 | +建议新手先学习完所有用法,再尝试自定义标签和检测。 |
| 8 | + |
| 9 | +下面开始正式教程。 |
| 10 | + |
| 11 | +--- |
| 12 | +# Hacknet 模组开发指南:标签检测与一次性触发系统 |
| 13 | + |
| 14 | +## 🎯 概述 |
| 15 | + |
| 16 | +本指南将教你创建一个 Hacknet 模组,实现以下功能: |
| 17 | +- 检测计算机是否包含特定标签 |
| 18 | +- 运行自定义 EXE 程序输出特定信息 |
| 19 | +- 确保每次连接节点只触发一次 |
| 20 | +- 支持存档持久化 |
| 21 | + |
| 22 | +## 📁 文件架构 |
| 23 | + |
| 24 | +``` |
| 25 | +YourMod/ |
| 26 | +├── YourModMain.cs // 插件主入口 |
| 27 | +├── ModCore.cs // 核心逻辑管理器 |
| 28 | +├── YourModStorage.cs // 数据存储系统 |
| 29 | +├── TestTagNode.cs // 标签节点定义 |
| 30 | +├── TestTagLoader.cs // XML标签加载器 |
| 31 | +└── TestTagExe.cs // 自定义可执行程序 |
| 32 | +``` |
| 33 | + |
| 34 | +## 🔧 详细实现说明 |
| 35 | + |
| 36 | +### 1. 插件主入口 (YourModMain.cs) |
| 37 | +```csharp |
| 38 | +// 作用:模组启动入口,注册所有组件 |
| 39 | +[BepInPlugin(ModGUID, ModName, ModVer)] |
| 40 | +public class YourModMain : HacknetPlugin |
| 41 | +{ |
| 42 | + public override bool Load() |
| 43 | + { |
| 44 | + new ModCore(); // 初始化核心管理器 |
| 45 | + // 注册自定义EXE程序 |
| 46 | + Pathfinder.Executable.ExecutableManager.RegisterExecutable<TestTagExe>("#TestTagExe#"); |
| 47 | + return true; |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +**关键点:** |
| 53 | +- 使用 `BepInPlugin` 属性声明模组信息 |
| 54 | +- 在 `Load()` 方法中初始化所有组件 |
| 55 | +- 必须使用 `ExecutableManager.RegisterExecutable` 注册EXE程序 |
| 56 | + |
| 57 | +### 2. 核心逻辑管理器 (ModCore.cs) |
| 58 | +```csharp |
| 59 | +// 作用:管理触发状态、处理存档事件 |
| 60 | +public class ModCore |
| 61 | +{ |
| 62 | + private static HashSet<string> triggeredComputers = new HashSet<string>(); |
| 63 | + |
| 64 | + public ModCore() |
| 65 | + { |
| 66 | + // 注册存档加载和保存事件 |
| 67 | + EventManager<SaveComputerLoadedEvent>.AddHandler(OnComputerLoaded); |
| 68 | + EventManager<SaveComputerEvent>.AddHandler(OnComputerSave); |
| 69 | + } |
| 70 | + |
| 71 | + public static bool CanTriggerOnComputer(string compId) |
| 72 | + { |
| 73 | + // 检查是否已触发,确保每次连接只触发一次 |
| 74 | + if (triggeredComputers.Contains(compId)) |
| 75 | + return false; |
| 76 | + triggeredComputers.Add(compId); |
| 77 | + return true; |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +**关键点:** |
| 83 | +- 使用 `HashSet` 跟踪已触发的计算机 |
| 84 | +- 通过事件系统处理存档的加载和保存 |
| 85 | +- `CanTriggerOnComputer` 方法是触发控制的核心 |
| 86 | + |
| 87 | +### 3. 数据存储系统 (YourModStorage.cs) |
| 88 | +```csharp |
| 89 | +// 作用:提供全局数据访问接口 |
| 90 | +public static class YourModStorage |
| 91 | +{ |
| 92 | + private static Dictionary<string, TestTagNode> nodes = new Dictionary<string, TestTagNode>(); |
| 93 | + |
| 94 | + public static void AddOrUpdate(string compId, TestTagNode node) |
| 95 | + { |
| 96 | + nodes[compId] = node; |
| 97 | + } |
| 98 | + |
| 99 | + public static bool HasNode(string compId) |
| 100 | + { |
| 101 | + return nodes.ContainsKey(compId); |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +**关键点:** |
| 107 | +- 使用静态字典存储所有标签节点 |
| 108 | +- 提供线程安全的访问方法 |
| 109 | +- 计算机ID作为字典键值 |
| 110 | + |
| 111 | +### 4. 标签节点定义 (TestTagNode.cs) |
| 112 | +```csharp |
| 113 | +// 作用:定义标签的数据结构 |
| 114 | +public class TestTagNode |
| 115 | +{ |
| 116 | + public bool HasTriggered { get; set; } = false; |
| 117 | + |
| 118 | + public static TestTagNode FromXml(ElementInfo info) |
| 119 | + { |
| 120 | + var node = new TestTagNode(); |
| 121 | + // 从XML属性读取触发状态 |
| 122 | + if (info.Attributes.TryGetValue("HasTriggered", out string triggeredStr)) |
| 123 | + { |
| 124 | + node.HasTriggered = bool.Parse(triggeredStr); |
| 125 | + } |
| 126 | + return node; |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +**关键点:** |
| 132 | +- `HasTriggered` 属性用于跨游戏会话的持久化 |
| 133 | +- `FromXml` 方法处理存档数据的反序列化 |
| 134 | + |
| 135 | +### 5. XML标签加载器 (TestTagLoader.cs) |
| 136 | +```csharp |
| 137 | +// 作用:解析计算机XML中的自定义标签 |
| 138 | +[ComputerExecutor("TestTag")] |
| 139 | +public class TestTagLoader : ContentLoader.ComputerExecutor |
| 140 | +{ |
| 141 | + public override void Execute(EventExecutor exec, ElementInfo info) |
| 142 | + { |
| 143 | + string compId = Comp.idName ?? Comp.name; |
| 144 | + var node = new TestTagNode(); |
| 145 | + YourModStorage.AddOrUpdate(compId, node); |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +**关键点:** |
| 151 | +- 使用 `[ComputerExecutor("TestTag")]` 属性声明标签处理器 |
| 152 | +- 在游戏加载计算机时自动调用 |
| 153 | +- 将标签信息存入全局存储 |
| 154 | + |
| 155 | +### 6. 自定义可执行程序 (TestTagExe.cs) |
| 156 | +```csharp |
| 157 | +// 作用:实现具体的检测和输出逻辑 |
| 158 | +public class TestTagExe : BaseExecutable |
| 159 | +{ |
| 160 | + public TestTagExe(Rectangle location, OS operatingSystem, string[] args) |
| 161 | + : base(location, operatingSystem, args) |
| 162 | + { |
| 163 | + // 获取目标计算机 |
| 164 | + var targetComp = os.connectedComp ?? os.thisComputer; |
| 165 | + string compId = targetComp.idName ?? targetComp.name; |
| 166 | + |
| 167 | + // 检查标签存在性 |
| 168 | + if (!YourModStorage.HasNode(compId)) |
| 169 | + { |
| 170 | + os.write("NO TestTag!"); |
| 171 | + needsRemoval = true; |
| 172 | + return; |
| 173 | + } |
| 174 | + |
| 175 | + // 检查触发权限 |
| 176 | + if (!ModCore.CanTriggerOnComputer(compId)) |
| 177 | + { |
| 178 | + os.write("Computer TESTED!"); |
| 179 | + needsRemoval = true; |
| 180 | + return; |
| 181 | + } |
| 182 | + |
| 183 | + // 执行主要逻辑 |
| 184 | + os.write("test complete"); |
| 185 | + needsRemoval = true; |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +**关键点:** |
| 191 | +- 继承 `BaseExecutable` 类 |
| 192 | +- 在构造函数中完成所有逻辑(因为程序立即结束) |
| 193 | +- 使用 `needsRemoval = true` 让程序执行后自动移除 |
| 194 | +- 三层检查机制确保正确触发 |
| 195 | + |
| 196 | +## 🎮 使用流程 |
| 197 | + |
| 198 | +### 1. 在计算机XML中添加标签 |
| 199 | +```xml |
| 200 | +<computer name="TestComputer" ip="123.45.67.89" security="2"> |
| 201 | + <!-- 其他计算机配置 --> |
| 202 | + <TestTag /> |
| 203 | +</computer> |
| 204 | +``` |
| 205 | + |
| 206 | +### 2. 在游戏中运行EXE |
| 207 | +``` |
| 208 | +run #TestTagExe# |
| 209 | +``` |
| 210 | + |
| 211 | +### 3. 预期输出结果 |
| 212 | + |
| 213 | +| 场景 | 输出内容 | 说明 | |
| 214 | +|------|----------|------| |
| 215 | +| 首次运行(有标签) | `test complete` | 正常触发 | |
| 216 | +| 重复运行(同一连接) | `Computer TESTED!` | 防止重复触发 | |
| 217 | +| 无标签计算机 | `NO TestTag!` | 标签检测失败 | |
| 218 | +| 重新连接后运行 | `test complete` | 连接重置后重新触发 | |
| 219 | + |
| 220 | +## 🔄 触发机制详解 |
| 221 | + |
| 222 | +### 会话级别控制 |
| 223 | +- **EXE实例级别**:`hasTriggeredThisSession` 确保同一程序实例不重复触发 |
| 224 | +- **连接会话级别**:`ModCore.CanTriggerOnComputer()` 确保同一连接只触发一次 |
| 225 | +- **游戏会话级别**:`TestTagNode.HasTriggered` 记录历史触发状态(用于存档) |
| 226 | + |
| 227 | +### 重置条件 |
| 228 | +当满足以下条件时,触发状态会被重置: |
| 229 | +- 玩家使用 `disconnect` 命令 |
| 230 | +- 连接到其他计算机 |
| 231 | +- 游戏重新加载 |
| 232 | + |
| 233 | +### 修改标签名称 |
| 234 | +将所有文件中的 `TestTag` 替换为你想要的标签名: |
| 235 | +```csharp |
| 236 | +// 在 TestTagLoader.cs 中 |
| 237 | +[ComputerExecutor("YourCustomTag")] |
| 238 | +``` |
| 239 | + |
| 240 | +### 修改输出信息 |
| 241 | +在 `TestTagExe.cs` 中修改 `os.write()` 的内容: |
| 242 | +```csharp |
| 243 | +os.write("你的自定义输出信息"); |
| 244 | +``` |
| 245 | + |
| 246 | + |
| 247 | + |
| 248 | +## ⚠️ 注意事项 |
| 249 | + |
| 250 | +1. **命名空间一致性**:确保所有文件使用相同的命名空间 |
| 251 | +2. **GUID唯一性**:修改 `ModGUID` 确保不与其他模组冲突 |
| 252 | +3. **XML标签大小写**:XML标签名称区分大小写 |
| 253 | +4. **存档兼容性**:修改标签结构时考虑旧存档的兼容性 |
| 254 | + |
0 commit comments