Skip to content

实战示例

几个常见任务的完整可复制代码。

注册新的子命令

最常见的扩展需求。

简单命令(无参数)

js
// plugins/skyblock/plugins/hello.js

skyblock.Command.registerAll({
    "hello": (origin) => {
        const player = origin.player;
        if (!player) return;
        player.sendMsg("Hello!");
    },
});

skyblock.Store.set("help",
    skyblock.Store.get("help") + "\n§e/is hello §7- 打招呼", true);

带参数和枚举

js
skyblock.Command.registerAll({
    "shop": {
        enums: {
            shop_action: ["open", "close", "reload"],
        },
        params: {
            shop_target: { type: ParamType.String, optional: true },
        },
        overloads: [
            ["shop_action"],
            ["shop_action", "shop_target"],
        ],
        callback: (origin, output, results) => {
            const player = origin.player;
            if (!player) return;
            switch (results.shop_action) {
                case "open":   return openShop(player, results.shop_target);
                case "close":  return closeShop(player);
                case "reload": return reloadShop(player);
            }
        },
    },
});

skyblock.Store.set("help",
    skyblock.Store.get("help") +
    "\n§e/is shop open|close|reload §a[target] §7- 商店", true);

必须在文件顶层注册

Command.setup()loadPlugins() 完成后立即调用一次。之后再 registerAll 不会生效。所以你的 Command.registerAll(...) 必须直接写在文件最外层,不能延迟到任何回调里。


自定义保护规则

三种思路对比

想做的事用哪个
在标准检查的基础上 额外放行 某些情况(如 VIP 豁免)skyblock.Event.guard
在标准检查通过后 额外拦截(如博物馆禁止破坏)mc.listen + 直接 return false
只想 观察 / 记录,不干预skyblock.Event.on

VIP 玩家在任何地方都能放方块

利用"可拦截"事件的白名单语义 —— 任意一个 fn 返回 true 即放行,所以你的 true叠加在内置规则之上:

js
skyblock.Event.guard("onPlaceBlock", (player, block) => {
    if (player.getTag?.("vip")) return true;   // VIP 豁免
    return false;                              // 让标准守卫继续判
});

博物馆区域禁止破坏(连岛主也不行)

白名单语义没法"否决"别人的放行,所以mc.listen 直接拦截:

js
const MUSEUM_ISLAND_ID = "abc123def456...";

mc.listen("onDestroyBlock", (player, block) => {
    if (player.isAdmin) return;   // 管理员豁免

    const id = skyblock.protect.findIslandId(block.pos);
    if (id === MUSEUM_ISLAND_ID) {
        player.tell("§c博物馆禁止破坏", 4);
        return false;             // 拦截
    }
});

mc.listen 与内置保护层是 独立两套监听,LSE 只要任一个返回 false 就拦截动作。

记录所有方块破坏

只想旁观,用 on 监听后置事件:

js
skyblock.Event.on("onDestroySignAfter", (player, block) => {
    log.info(`${player.realName} 破坏了 ${block.type} @ ${block.pos}`);
});

注意 onDestroyBlock 没有对应的 After 通知事件,如果你要监听所有破坏后的副作用,得用 mc.listen("onDestroyBlock", ...) 不返回(不返回相当于放行)。


跨插件通信

提供查询能力给其他插件

level.js 暴露查询接口:

js
// plugins/skyblock/plugins/level.js
const ranking = {};   // islandId -> level

skyblock.Store.register(
    "islandLevel:get",
    (islandId) => ranking[islandId] || 0,
    true,
);

challenges.js 使用:

js
const lv = skyblock.Store.call("islandLevel:get", player.islandId) ?? 0;
if (lv < 50) return player.sendMsg("等级不够");

优雅检测可选依赖

js
// 在挑战插件里
if (skyblock.Store.has("islandLevel:get")) {
    // level 插件存在,可以做等级相关功能
    const lv = skyblock.Store.call("islandLevel:get", player.islandId);
} else {
    // level 插件没装,退化处理
}

发布自定义事件

js
// shop.js
function processPurchase(player, item, price) {
    player.addMoney?.(-price);
    player.giveItem(item);

    skyblock.Event.emit("shop:purchased", {
        xuid:  player.xuid,
        item:  item.type,
        price: price,
    });
}

// achievements.js(订阅)
skyblock.Event.on("shop:purchased", ({ xuid }) => {
    incrementCounter(xuid, "shop_count");
});

事件名加 <插件名>: 前缀避免冲突。


监听岛屿生命周期

新岛屿欢迎

js
skyblock.Event.on("island:created", (xuid, islandId) => {
    const p = mc.getPlayer(xuid);
    p?.setTitle("§a欢迎来到空岛!", 0);
    p?.setTitle("§7祝你玩得愉快", 3);
});

岛屿解散时清理外部资源

js
skyblock.Event.on("island:removed", ({ islandId, members }) => {
    // 例:删自己插件中和这个岛屿关联的所有数据
    delete myStorage.byIsland[islandId];
    myFile.set("byIsland", myStorage.byIsland);

    // 通知所有原成员
    for (const xuid of members) {
        mc.getPlayer(xuid)?.sendMsg("§c你所在的岛屿已被删除");
    }
});

进岛 / 离岛标题提示

js
skyblock.Event.on("player:enterIsland", (player, id) => {
    const isOwn = player.islandId === id;
    const head  = isOwn ? "§a欢迎回来" : "§a你正在访问";
    const name  = skyblock.island.getIslandName(id) ?? "未命名";
    player.setTitle(head, 2);
    player.setTitle(`§c${name}`, 3);
});

skyblock.Event.on("player:leaveIsland", (player, id) => {
    const name = skyblock.island.getIslandName(id) ?? "未命名";
    player.tell(`你离开了 ${name} 的岛屿`, 4);   // 4 = actionbar
});

持久化数据

Store 是内存的,重启就没了。要持久化用 LSE 的 JsonConfigFile:

js
const FILE = new JsonConfigFile(
    "./plugins/skyblock/plugins/myplugin/data.json",
    "{}"
);

const players = FILE.init("players", {});   // 初始化默认值

// 改完写盘
function recordVisit(xuid) {
    players[xuid] = (players[xuid] || 0) + 1;
    FILE.set("players", players);
}

放自己专属的子目录(plugins/skyblock/plugins/<your-plugin>/),不要往 runtime/ 里塞东西


多语言

注册自己的文案

js
skyblock.i18n.register("zh_CN", {
    "myplugin.usage":   "用法: /is mycmd <玩家>",
    "myplugin.done":    "§a完成: {name}",
    "myplugin.no_perm": "§c你没有权限",
});

skyblock.i18n.register("en_US", {
    "myplugin.usage":   "Usage: /is mycmd <player>",
    "myplugin.done":    "§aDone: {name}",
    "myplugin.no_perm": "§cNo permission",
});

使用

js
player.sendMsg(skyblock.i18n.tr("myplugin.done", { name: target }));

key 用 <插件名>. 前缀,避免与内置文案冲突。


完整最小示例

一个集成了上面多种能力的完整扩展模板,保存为 plugins/skyblock/plugins/welcome.js 即可运行:

js
// 多语言
skyblock.i18n.register("zh_CN", {
    "welcome.first_island": "§a恭喜创建你的第一个岛屿: §e{name}",
    "welcome.cmd_count":    "§b你已经在岛上停留过 §e{n} §b次",
});

// 持久化
const FILE = new JsonConfigFile(
    "./plugins/skyblock/plugins/welcome/data.json", "{}"
);
const visits = FILE.init("visits", {});   // xuid -> 次数

// 监听创建岛屿
skyblock.Event.on("island:created", (xuid, islandId) => {
    const p = mc.getPlayer(xuid);
    p?.sendMsg(skyblock.i18n.tr("welcome.first_island",
        { name: skyblock.island.getIslandName(islandId) }));
});

// 监听进岛(节流)
const STAY_MIN = 60_000;   // 1 分钟
const _last = new Map();
skyblock.Event.on("player:enterIsland", (player, id) => {
    if (player.islandId !== id) return;          // 只算回自己家
    const now = Date.now();
    if (now - (_last.get(player.xuid) ?? 0) < STAY_MIN) return;
    _last.set(player.xuid, now);

    visits[player.xuid] = (visits[player.xuid] || 0) + 1;
    FILE.set("visits", visits);
});

// 命令查询
skyblock.Command.registerAll({
    "visits": (origin) => {
        const p = origin.player;
        if (!p) return;
        const n = visits[p.xuid] || 0;
        p.sendMsg(skyblock.i18n.tr("welcome.cmd_count", { n }));
    },
});

// 帮助文本
skyblock.Store.set("help",
    skyblock.Store.get("help") + "\n§e/is visits §7- 查看你回家次数", true);

这个例子涵盖了:多语言注册 / 持久化 / 事件监听 + 节流 / 命令注册 / 帮助文本追加。基本能作为新扩展的起点模板。