Skip to content

Embedding

Himxh edited this page Oct 4, 2026 · 1 revision

嵌入式调用 / Embedding

定位 / Positioning

本章汇总从其他插件调用 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.


三种典型场景 / Three Typical Scenarios

场景一:配置即脚本 / Config-as-Script

问题:你的插件需要让用户配置"服务器活动时做什么",但配置格式无法表达条件逻辑。

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.

场景二:插件生成脚本 / Plugin-Generated 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.

场景三:跨插件共享状态 / Cross-Plugin State

问题:多个插件需要共享数据,脚本要读取。

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.


推荐做法 / Recommended Practices

检查 HimDSL 是否可用 / Check HimDSL Availability

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.

记录错误但不崩溃 / Log Errors, Do Not Crash

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.

缓存编译结果 / Cache Compilation

脚本文件不常变时,可以只编译一次,缓存在内存里:

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.

从主线程调用 run / Call run from the Main Thread

if (!Bukkit.isPrimaryThread()) {
    Bukkit.getScheduler().runTask(plugin, () -> himdsl.run(script));
} else {
    himdsl.run(script);
}

不推荐的做法 / Practices to Avoid

不要覆盖内置占位符 / Do Not Override Built-ins

// ✗ 影响所有脚本
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 存业务数据 / Do Not Store Business Data

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.

不要在 onDisable 里调用 API / Do Not Call the API in onDisable

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.

不要频繁 run / Do Not run Frequently

// ✗ 每次都重新解析、收集定义、执行
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.


一个完整的集成示例 / A Complete Integration Example

需求:你的插件管理"任务系统",用户想自定义"任务完成时执行脚本"。

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.


调试集成 / Debugging Integrations

集成出错时,先确认:

When integration misbehaves, verify:

  1. plugin.yml 里声明了 softdepend: [HimDSL]。

  2. onEnable 里成功拿到了 HimDSLAPI 实例。

  3. 脚本文件确实存在,路径正确。

  4. compile 命令从控制台手动执行能通过。

  5. 从主线程调用 run。

  6. 检查 isSuccess(),把 getErrorMessage() 写进日志。

  7. softdepend: [HimDSL] declared in plugin.yml.

  8. HimDSLAPI obtained in onEnable.

  9. Script file exists, path correct.

  10. compile passes when run manually from the console.

  11. run called from the main thread.

  12. Check isSuccess(), log getErrorMessage().

打开 HimDSL 调试:

Enable HimDSL debug:

/himdsl debug on
/himdsl debug codeon

观察控制台,可以看到 HimDSL 内部的执行流程。这比在你的插件里加日志更直观。

Watch the console for HimDSL internals — more direct than adding your own logs.


小结 / Summary

  • 三种场景:配置即脚本、生成脚本、跨插件共享状态。

  • 共享状态用 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.

  • run from 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 run frequently.

  • Debug with HimDSL's debug on.


扩展 API 部分回顾 / Extension API Review

扩展 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.


下一步 / Next Steps

最后一部分是示例脚本。第十章 [递归示例 / Ex-Recursion] 从斐波那契、阶乘这类经典例子开始。

The final part is Example Scripts. Chapter [递归示例 / Ex-Recursion] starts with Fibonacci, factorial, and other classics.


(第五十四章完 / End of Chapter 54)

HimDSL 文档 / Documentation

入门 / Getting Started

语言基础 / Language Basics

控制流 / Control Flow

函数 / Functions

数据结构 / Data Structures

自定义类型 / Custom Types

Minecraft 集成 / Minecraft Integration

事件与调度 / Events & Scheduling

GUI 系统 / GUI System

内置函数参考 / Built-ins Reference

进阶 / Advanced

扩展 API / Extension API

你真的需要 API 吗?/ Do you really need the API?

Clone this wiki locally