Skip to content

feat:load js script plugin - #1197

Closed
Controllerdestiny wants to merge 9 commits into
masterfrom
ec
Closed

feat:load js script plugin#1197
Controllerdestiny wants to merge 9 commits into
masterfrom
ec

Conversation

@Controllerdestiny

@Controllerdestiny Controllerdestiny commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

添加插件

  • 插件已加入解决方案 (Plugin.sln)
  • 插件项目已导入template.targets ()
  • 插件信息已添加至对应manifest.json
  • 插件的文件夹名字和插件的插件项目名字一样 (XXX/XXX.csproj)
  • 添加插件单独的README.md文件 (XXX/README.md)
  • 插件可以正常工作

更新插件/修复BUG

  • 插件已修改版本号
  • 更新插件README.md中的更新日志
  • 插件可以正常工作

其他

  • ❤️熙恩我喜欢你

Sourcery 摘要

引入共享的 JavaScript 脚本基础设施、独立脚本插件、可配置的 NPC AI,以及跨 Economics 插件的基于占位符的状态显示功能。

新功能:

  • 添加独立的 JavaScript 插件宿主,用于加载、重新加载和运行基于脚本的 TShock 扩展。
  • 添加可通过 JavaScript 配置的 NPC AI,支持生命周期钩子、宿主函数、安全限制、重新加载,以及示例 Boss 脚本。
  • 添加可自定义的玩家状态显示内容,并提供用于显示玩家、世界、游戏玩法、生物群系、渔夫、货币、RPG 和技能信息的内置占位符。

错误修复:

  • 修复玩家 Ping 收集问题,并通过占位符系统公开 Ping 值。

增强功能:

  • 使用基于占位符的消息渲染替代旧版玩家状态事件模型。
  • 将 JavaScript 执行整合到可复用的 Economics.Script 运行时中,支持缓存、重新加载检测、可配置的执行模式、预处理、宿主函数和资源保护。
  • 将技能脚本迁移到共享脚本运行时,并支持持久化或可重置的脚本全局变量。
  • 更新 NPC、RPG、技能和核心插件的集成与文档,以支持新的脚本和状态显示功能。

构建:

  • 添加 Economics.Plugin 和 Economics.Script 项目,并更新解决方案和项目依赖项。

文档:

  • 编写 JavaScript 插件宿主、NPC AI 脚本、脚本生命周期与 API、技能脚本执行行为,以及可自定义状态显示配置的文档。

杂项:

  • 移除已废弃的玩家状态事件模型和旧版技能 JavaScript 解释器实现。
  • 将受影响的插件版本提升至 3.1.0.0。
Original summary in English

Summary by Sourcery

Introduce shared JavaScript scripting infrastructure, a standalone script plugin, configurable NPC AI, and placeholder-driven status displays across the Economics plugins.

New Features:

  • Add a standalone JavaScript plugin host for loading, reloading, and running script-based TShock extensions.
  • Add JavaScript-configurable NPC AI with lifecycle hooks, host functions, safety limits, reload support, and example boss scripts.
  • Add customizable player status display content with built-in placeholders for player, world, gameplay, biome, angler, currency, RPG, and skill information.

Bug Fixes:

  • Fix player ping collection and expose ping values through the placeholder system.

Enhancements:

  • Replace the legacy player status event model with placeholder-based message rendering.
  • Consolidate JavaScript execution into a reusable Economics.Script runtime with caching, reload detection, configurable execution modes, preprocessing, host functions, and resource protections.
  • Migrate skill scripting to the shared script runtime and support persistent or resettable script globals.
  • Update NPC, RPG, skill, and core plugin integrations and documentation for the new scripting and status display capabilities.

Build:

  • Add the Economics.Plugin and Economics.Script projects and update the solution and project dependencies.

Documentation:

  • Document the JavaScript plugin host, NPC AI scripts, script lifecycle and APIs, skill script execution behavior, and customizable status display configuration.

Chores:

  • Remove the obsolete player countertop event models and legacy skill JavaScript interpreter implementation.
  • Bump affected plugin versions to 3.1.0.0.

@Controllerdestiny
Controllerdestiny requested a review from a team as a code owner September 3, 2026 11:17
@Controllerdestiny

Copy link
Copy Markdown
Contributor Author

woc

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了 2 个问题

面向 AI Agent 的提示
请处理本次代码审查中的评论:

## 单独评论

### 评论 1
<location path="src/Economics.Script/Sources/FileScriptSourceProvider.cs" line_range="32-33" />
<code_context>
+            return false;
+        }
+
+        var candidate = Path.GetFullPath(Path.Combine(this._rootFull, key));
+        if (!candidate.StartsWith(this._rootFull, StringComparison.OrdinalIgnoreCase))
+        {
+            return false;
</code_context>
<issue_to_address>
**🚨 issue (security):** 路径包含性检查会接受名称仅与根路径前缀相同的同级路径,因此像 `/server/SkillScripts` 这样的根路径也会接受 `/server/SkillScriptsBackup/script.js`。因此,技能配置中如果包含一个解析到此类同级文件的路径,就会加载并执行配置目录之外的脚本。

**触发条件:** 当脚本键解析到的目录路径以配置的根路径字符串开头,但实际上并不位于该根路径下时。

**建议修复:** 要求候选路径等于根路径,或以根路径加平台目录分隔符开头;也可以在规范化后比较相对路径。
</issue_to_address>

### 评论 2
<location path="src/Economics.Script/ScriptRuntime.cs" line_range="74-77" />
<code_context>
+    /// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
+    /// 返回 <c>false</c> 而不会抛出/刷屏。成功时返回 <c>true</c>。
+    /// </summary>
+    public bool TryInvoke(string entryFunction, params object?[] args)
+    {
+        lock (this._sync)
+        {
+            this.EnsureBuilt();
+            if (!this._loaded || this._engine is null)
+            {
+                return false;
+            }
+
</code_context>
<issue_to_address>
**nitpick:** `TryInvoke` 的文档承诺调用失败时不会输出错误,但两个实现都会在发生异常时调用配置的 `ErrorHandler`;插件处理程序会将这些异常记录到控制台。因此,尽管文档如此约定,失败的可选事件回调实际上仍会产生错误输出。

**触发条件:** 当可选脚本事件函数抛出异常,且宿主配置了错误处理器时。

**建议修复:** 要么更新文档,说明会调用配置的错误处理器;要么针对静默的 `TryInvoke` 约定单独抑制或记录错误。

```suggestion
    /// <summary>
    /// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
    /// 返回 <c>false</c> 而不会抛出;调用失败时会调用配置的错误处理器。成功时返回 <c>true</c>。
    /// </summary>
```
</issue_to_address>

Sourcery 对开源项目免费——如果你喜欢我们的审查结果,请考虑分享它们 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会利用反馈来改进审查结果。
Original comment in English

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/Economics.Script/Sources/FileScriptSourceProvider.cs" line_range="32-33" />
<code_context>
+            return false;
+        }
+
+        var candidate = Path.GetFullPath(Path.Combine(this._rootFull, key));
+        if (!candidate.StartsWith(this._rootFull, StringComparison.OrdinalIgnoreCase))
+        {
+            return false;
</code_context>
<issue_to_address>
**🚨 issue (security):** The path-containment check accepts sibling paths whose names merely share the root prefix, so a root such as `/server/SkillScripts` also accepts `/server/SkillScriptsBackup/script.js`. A skill configuration containing a path that resolves to such a sibling file therefore loads and executes a script outside the configured directory.

**Triggers:** When a script key resolves to a directory whose path starts with the configured root string but is not below that root.

**Suggested fix:** Require the candidate to equal the root or start with the root followed by the platform directory separator, or compare relative paths after canonicalization.
</issue_to_address>

### Comment 2
<location path="src/Economics.Script/ScriptRuntime.cs" line_range="74-77" />
<code_context>
+    /// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
+    /// 返回 <c>false</c> 而不会抛出/刷屏。成功时返回 <c>true</c>。
+    /// </summary>
+    public bool TryInvoke(string entryFunction, params object?[] args)
+    {
+        lock (this._sync)
+        {
+            this.EnsureBuilt();
+            if (!this._loaded || this._engine is null)
+            {
+                return false;
+            }
+
</code_context>
<issue_to_address>
**nitpick:** The `TryInvoke` documentation promises that failed calls do not print errors, but both implementations invoke the configured `ErrorHandler` on exceptions; the plugin handlers log those exceptions to the console. Failed optional event callbacks therefore do produce error output despite the documented contract.

**Triggers:** When an optional script event function throws and the host configured an error handler.

**Suggested fix:** Either update the documentation to state that the configured error handler is called, or suppress/log separately for the quiet `TryInvoke` contract.

```suggestion
    /// <summary>
    /// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
    /// 返回 <c>false</c> 而不会抛出;调用失败时会调用配置的错误处理器。成功时返回 <c>true</c>。
    /// </summary>
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +32 to +33
var candidate = Path.GetFullPath(Path.Combine(this._rootFull, key));
if (!candidate.StartsWith(this._rootFull, StringComparison.OrdinalIgnoreCase))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): 路径包含性检查会接受名称仅与根路径前缀相同的同级路径,因此像 /server/SkillScripts 这样的根路径也会接受 /server/SkillScriptsBackup/script.js。因此,技能配置中如果包含一个解析到此类同级文件的路径,就会加载并执行配置目录之外的脚本。

触发条件: 当脚本键解析到的目录路径以配置的根路径字符串开头,但实际上并不位于该根路径下时。

建议修复: 要求候选路径等于根路径,或以根路径加平台目录分隔符开头;也可以在规范化后比较相对路径。

Original comment in English

🚨 issue (security): The path-containment check accepts sibling paths whose names merely share the root prefix, so a root such as /server/SkillScripts also accepts /server/SkillScriptsBackup/script.js. A skill configuration containing a path that resolves to such a sibling file therefore loads and executes a script outside the configured directory.

Triggers: When a script key resolves to a directory whose path starts with the configured root string but is not below that root.

Suggested fix: Require the candidate to equal the root or start with the root followed by the platform directory separator, or compare relative paths after canonicalization.

Comment on lines +74 to +77
/// <summary>
/// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
/// 返回 <c>false</c> 而不会抛出/刷屏。成功时返回 <c>true</c>。
/// </summary>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: TryInvoke 的文档承诺调用失败时不会输出错误,但两个实现都会在发生异常时调用配置的 ErrorHandler;插件处理程序会将这些异常记录到控制台。因此,尽管文档如此约定,失败的可选事件回调实际上仍会产生错误输出。

触发条件: 当可选脚本事件函数抛出异常,且宿主配置了错误处理器时。

建议修复: 要么更新文档,说明会调用配置的错误处理器;要么针对静默的 TryInvoke 约定单独抑制或记录错误。

Suggested change
/// <summary>
/// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
/// 返回 <c>false</c> 而不会抛出/刷屏。成功时返回 <c>true</c>。
/// </summary>
/// <summary>
/// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
/// 返回 <c>false</c> 而不会抛出;调用失败时会调用配置的错误处理器。成功时返回 <c>true</c>。
/// </summary>
Original comment in English

nitpick: The TryInvoke documentation promises that failed calls do not print errors, but both implementations invoke the configured ErrorHandler on exceptions; the plugin handlers log those exceptions to the console. Failed optional event callbacks therefore do produce error output despite the documented contract.

Triggers: When an optional script event function throws and the host configured an error handler.

Suggested fix: Either update the documentation to state that the configured error handler is called, or suppress/log separately for the quiet TryInvoke contract.

Suggested change
/// <summary>
/// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
/// 返回 <c>false</c> 而不会抛出/刷屏。成功时返回 <c>true</c>。
/// </summary>
/// <summary>
/// 调用指定入口函数(用于可选的事件钩子)。若入口函数不存在、不可调用、未加载或调用失败,
/// 返回 <c>false</c> 而不会抛出;调用失败时会调用配置的错误处理器。成功时返回 <c>true</c>。
/// </summary>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant