Repository navigation
Embedding
本章汇总从其他插件调用 HimDSL 的场景、实践建议和常见问题。内容承接前两章,但更侧重怎么用得好。
This chapter collects calling HimDSL from other plugins — scenarios, practices, and pitfalls. It builds on the previous two chapters with a focus on doing it well.
问题:你的插件需要让用户配置"服务器活动时做什么",但配置格式无法表达条件逻辑。
Problem: your plugin needs users to configure "what happens on server events", but the config format cannot express conditionals.
方案:让用户写 .himdsl 脚本,你的插件在合适时机调用。
Solution: let users write .himdsl scripts; your plugin invokes them at the right moments.
public void onServerEvent(Event e) {
File script = new File(himdslFolder, "handlers/server_event.himdsl");
if (script.exists()) {
api.run(script);
}
}典型:HimDungeons 这类需要"地牢失败时做什么"的大型插件,把行为开放给用户脚本。
Typical: large plugins like HimDungeons open "what happens on dungeon failure" to user scripts.
问题:用户在图形界面里配置规则,你的插件要把规则转换成可执行逻辑。
Problem: the user configures rules in a GUI; your plugin needs to turn them into executable logic.
方案:生成 .himdsl 文本,saveScript 保存,按需 run。
Solution: generate .himdsl text, saveScript, run when needed.
String code = generateScript(userRules);
api.saveScript("generated/rules.himdsl", code);
api.run(new File(himdslFolder, "generated/rules.himdsl"));典型:可视化规则编辑器、自动化工具。
Typical: visual rule editors, automation tools.
问题:多个插件需要共享数据,脚本要读取。
Problem: multiple plugins share state that scripts should read.
方案:用 PlaceholderAPI,不用 HimDSL 的自定义占位符。
Solution: use PlaceholderAPI, not HimDSL's custom placeholders.
// 你的插件注册 PAPI 扩展
new MyExpansion().register();脚本中:
In a script:
double value = toDouble(papi("myplugin_value", @p));
原因:不受 HimDSL 语法限制,也与 HimDSL 的加载/重载解耦。
Reason: no HimDSL grammar restrictions, and decoupled from HimDSL's load/reload cycle.
private HimDSLAPI himdsl;
@Override
public void onEnable() {
himdsl = Bukkit.getServicesManager().load(HimDSLAPI.class);
if (himdsl == null) {
getLogger().info("HimDSL 未安装,脚本功能已禁用");
}
}
private boolean hasHimDSL() {
return himdsl != null;
}每个调用点检查一次,不要让 NullPointerException 成为用户的第一印象。
Check at every call site. Do not let NullPointerException be the user's first impression.
HimDSLAPI.ExecutionResult r = himdsl.run(script);
if (!r.isSuccess()) {
getLogger().warning("脚本执行失败: " + r.getErrorMessage());
// 不让整个插件崩溃,只是记录
}脚本是用户内容。用户内容出错时,插件应当继续工作,而不是因为一个坏脚本整个不可用。
Scripts are user content. When user content fails, the plugin should keep working rather than becoming unusable due to a bad script.
脚本文件不常变时,可以只编译一次,缓存在内存里:
When a script rarely changes, compile once and cache:
private boolean compiled = false;
private void ensureCompiled(File script) {
if (compiled) return;
HimDSLAPI.CompileResult r = himdsl.compile(script);
if (!r.isSuccess()) {
getLogger().warning("脚本语法错误: " + r.getErrorMessage());
return;
}
compiled = true;
}但 run 每次都重新解析——HimDSL 的 run 不支持"复用编译结果"。
But run re-parses each time — HimDSL's run does not support reusing a compile result.
if (!Bukkit.isPrimaryThread()) {
Bukkit.getScheduler().runTask(plugin, () -> himdsl.run(script));
} else {
himdsl.run(script);
}// ✗ 影响所有脚本
himdsl.registerPlaceholder("player", args -> { ... });
himdsl.registerPlaceholder("server", args -> { ... });内置占位符是 HimDSL 的一部分。覆盖它们会破坏其他所有脚本的行为,包括那些跟你的插件无关的。
Built-in placeholders belong to HimDSL. Overriding them breaks every other script, including those unrelated to your plugin.
HimDSL 数据目录由用户管理——用户可能删除、移动、重命名脚本。不要把关键业务数据放在那里。
HimDSL's data folder belongs to the user — they may delete, move, or rename scripts. Do not put critical business data there.
需要持久化时,用自己的数据目录。
Use your own data folder for persistence.
HimDSL 卸载顺序不确定。onDisable 里 himdsl 可能已经被清理,调用会导致空指针。
HimDSL's unload order is not guaranteed. In onDisable, himdsl may already be cleared; calling it can NPE.
如果确实需要在禁用时执行脚本,写一个 onServerShutdown.himdsl,交给 HimDSL 自己管理。
If you must run a script at shutdown, write an onServerShutdown.himdsl and let HimDSL handle it.
// ✗ 每次都重新解析、收集定义、执行
for (Player p : Bukkit.getOnlinePlayers()) {
api.run(script);
}run 的开销不小。频繁调用会拖慢服务器。需要"每个玩家执行一次"时,用参数化调用:脚本一次处理所有玩家,或从脚本里遍历。
run is not cheap. Frequent calls hurt. For "once per player", parameterize: one script handles everyone, or iterate inside the script.
当前 API 不支持传参(run(File) 版本不接收参数)。多玩家场景,让脚本自己用 @a 遍历。
The current API takes no arguments (run(File)). For multiple players, let the script iterate with @a.
需求:你的插件管理"任务系统",用户想自定义"任务完成时执行脚本"。
Requirement: your plugin manages "quests"; users want custom scripts on quest completion.
public class QuestPlugin extends JavaPlugin {
private HimDSLAPI himdsl;
private File himdslFolder;
@Override
public void onEnable() {
himdsl = Bukkit.getServicesManager().load(HimDSLAPI.class);
Plugin himdslPlugin = Bukkit.getPluginManager().getPlugin("HimDSL");
if (himdslPlugin != null) {
himdslFolder = himdslPlugin.getDataFolder();
getLogger().info("HimDSL 集成已启用");
}
}
public void onQuestComplete(Player player, String questId) {
if (himdsl == null || himdslFolder == null) return;
// 用户约定:脚本放在 quests/ 下,文件名是 questId.himdsl
File script = new File(himdslFolder, "quests/" + questId + ".himdsl");
if (!script.exists()) return;
// 从主线程执行
Bukkit.getScheduler().runTask(this, () -> {
HimDSLAPI.ExecutionResult r = himdsl.run(script);
if (!r.isSuccess()) {
getLogger().warning(
"任务脚本 " + questId + " 执行失败: " + r.getErrorMessage()
);
}
});
}
}用户侧:在 plugins/HimDSL/quests/dragon_slayer.himdsl 里写:
On the user side, in plugins/HimDSL/quests/dragon_slayer.himdsl:
int main() {
var players = @a;
for (int i = 0; i < players.size(); i = i + 1) {
var p = players.get(i);
runcmd(p.name, "title @s title {\"text\":\"任务完成\"}");
runcmd("CONSOLE", "give " + p.name + " diamond 10");
}
}
这样职责清晰:QuestPlugin 只管"任务完成"这个时刻,具体做什么由用户脚本定义。
Responsibilities stay clean: QuestPlugin handles "the moment a quest completes"; the script defines what happens.
集成出错时,先确认:
When integration misbehaves, verify:
-
plugin.yml里声明了softdepend: [HimDSL]。 -
onEnable里成功拿到了HimDSLAPI实例。 -
脚本文件确实存在,路径正确。
-
compile命令从控制台手动执行能通过。 -
从主线程调用
run。 -
检查
isSuccess(),把getErrorMessage()写进日志。 -
softdepend: [HimDSL]declared inplugin.yml. -
HimDSLAPIobtained inonEnable. -
Script file exists, path correct.
-
compilepasses when run manually from the console. -
runcalled from the main thread. -
Check
isSuccess(), loggetErrorMessage().
打开 HimDSL 调试:
Enable HimDSL debug:
/himdsl debug on
/himdsl debug codeon
观察控制台,可以看到 HimDSL 内部的执行流程。这比在你的插件里加日志更直观。
Watch the console for HimDSL internals — more direct than adding your own logs.
-
三种场景:配置即脚本、生成脚本、跨插件共享状态。
-
共享状态用 PlaceholderAPI,不用自定义占位符。
-
总是检查 HimDSL 是否可用。
-
记录错误但不崩溃。
-
从主线程
run。 -
不要覆盖内置占位符。
-
不要存业务数据到 HimDSL 目录。
-
不要在
onDisable里调用 API。 -
不要频繁
run。 -
调试时打开 HimDSL 的
debug on。 -
Three scenarios: config-as-script, generated scripts, cross-plugin state.
-
Use PlaceholderAPI for shared state, not custom placeholders.
-
Always check HimDSL availability.
-
Log errors, do not crash.
-
runfrom the main thread. -
Do not override built-in placeholders.
-
Do not store business data in HimDSL's folder.
-
Do not call the API in
onDisable. -
Do not
runfrequently. -
Debug with HimDSL's
debug on.
扩展 API 部分到此结束。三章覆盖了:HimDSLAPI 接口、自定义占位符的限制、跨插件集成实践。
The Extension API section ends here. Three chapters covered: the HimDSLAPI interface, the limits of custom placeholders, and cross-plugin integration practices.
核心结论:
Key conclusions:
-
从 Java 调 HimDSL 主要用
compile、run、saveScript三个方法。 -
跨插件共享数据 优先走 PlaceholderAPI。
-
HimDSL 的数据目录属于用户,不要往里放业务数据。
-
一切从主线程。
-
Java → HimDSL mainly uses
compile,run,saveScript. -
Cross-plugin data prefers PlaceholderAPI.
-
HimDSL's data folder belongs to the user; keep business data out.
-
Everything from the main thread.
最后一部分是示例脚本。第十章 [递归示例 / Ex-Recursion] 从斐波那契、阶乘这类经典例子开始。
The final part is Example Scripts. Chapter [递归示例 / Ex-Recursion] starts with Fibonacci, factorial, and other classics.
(第五十四章完 / End of Chapter 54)
- 首页 / Home
- 安装与快速开始 / Installation & Quick Start
- 第一个脚本 / Your First Script
- 命令速查 / Command Reference
- 自动执行脚本 / Auto-run Scripts
- 定义函数 / Defining Functions
- 函数重载 / Function Overloading
- Lambda 表达式 / Lambda Expressions
- 函数式接口 / Functional Interfaces
- 选择器总览 / Selectors Overview
- 选择器过滤器 / Selector Filters
- EntityRef 引用 / EntityRef
- 占位符参考 / Placeholders Reference
- PlaceholderAPI 桥接 / PAPI Bridge
- 创建 GUI / Creating a GUI
- GUISlot 槽位 / GUISlot
- 点击回调 / Click Callbacks
- onClose 回调 / onClose Callback
- GUI 刷新 / GUI Refresh
- 数学函数 / Math Built-ins
- 高精度计算 / BigDecimal Built-ins
- 字符串函数 / String Built-ins
- 集合函数 / Collection Built-ins
- 文件 I/O / File I/O
- 命令与 sleep / Commands & sleep