Q Shop

Q Shop

适用于Forge 1.20.1的灵活服务端商店模组,包含完整的交易GUI、自定义货币、购买/出售/以物易物/命令交易、购买限制、游戏内编辑及KubeJS集成。

杂项

Q Shop

Q Shop 是一个适用于 Minecraft Forge 1.20.1 的服务端商店模组。你可以创建一个或多个商店,通过友好的游戏内 GUI(或通过命令 / KubeJS)为商店填充交易,使用基于每位玩家钱包的自定义非物品货币,并让你的玩家购买、出售、以物易物或触发服务器命令——所有功能均支持可配置的数量、购买限制和任务/阶段要求。

没有强制性的模组依赖。 可在纯净的 Forge 1.20.1 服务器上运行。KubeJS、FTB Quests 和 GameStages 是可选模组,仅在需要使用其各自功能(脚本 / 任务门槛 / 阶段门槛)时才需要。

特性







交易 GUI

  • 在同一个网格中支持购买、出售、物品对物品的易物和命令交易。
  • 对于易物交易,可以选择在物品之外额外收取少量货币费用。
  • 命令交易在购买时运行服务器命令——可选择使用物品或货币支付。
  • 每笔交易支持自定义显示名称、描述(支持 § 颜色代码)以及用于槽位的显示物品。
  • 每笔交易数量可配置:可根据你的余额/库存购买任意数量,并实时预览总价。

自定义货币

  • 支持任意数量的非物品货币(例如金币、点数、代币),每种货币都有名称和颜色。
  • 存储在每位玩家的持久化钱包中——没有物品刷屏,没有箱子。
  • 通过 /qshop currency give|take|set、/qshop currency create 或 KubeJS 进行管理。
  • 在 GUI 中使用 K/M/B 缩写进行大数字安全格式化。

商店与子商店

  • 支持多个商店,可通过 ID 或 UUID 识别。
  • 每个商店都有一个可滚动的 标签页列表(子商店),并带有图标。
  • 每个商店都有一个 默认货币(显示在 GUI 底部的余额)——可在创建商店时设置(命令 / KubeJS)或稍后在 编辑商店 对话框中设置。
  • 商店数据存储在每商店的 JSON 文件中(world/serverconfig/qshop/shops/)——完全可手动编辑。
  • 游戏内编辑(创造模式 + OP):通过拖放添加/移除/复制/重新排序条目,使用完整的物品选择器(包括 NBT)编辑物品,重命名标签页,编辑商店信息(名称、图标、默认货币)。

购买限制

  • 每个条目有 全局(服务器范围)和 每位玩家 限制,以物品数量计算。
  • 重置周期:从不 / 每日 / 每周 / 每月。

要求(门槛)

  • 将条目和整个标签页置于 FTB Quests 或 GameStages / KubeJS 阶段之后。
  • 这些是 可选的、按交易计算的特性:只有在交易上实际配置了要求时,交易才会受到影响。
  • 阶段检测需要安装 GameStages 模组(或 KubeJS 的阶段系统)。
  • 如果你配置了要求但未安装相应的模组,该要求将被视为未满足(交易保持锁定)——因此只在安装了这些模组的服务器上设置任务/阶段门槛。

购买时执行命令

  • 购买后执行一条或多条命令,支持占位符:%player%、%player_uuid%、%shop%、%shop_uuid%、%entry%、%units%、%items%、%price%、%currency%、%multiplier%。
  • 每条命令可配置 OP 等级(控制台级或玩家级)和静默执行。

细节优化

  • 自定义物品提示、彩色文本、淡入淡出遮罩、平滑滚动——GUI 专为易用性而设计。
  • 交易反馈消息可以发送到聊天栏或动作栏(statsMessage 区域),以避免聊天栏刷屏——可在服务器配置中切换。
  • 编辑模式状态会在关闭界面后保留(创造模式),生存模式始终会强制关闭它。

命令

/qshop open <shop> [player]              打开商店(OP 可为其他玩家打开)
/qshop list                               列出所有商店
/qshop balance                            显示你的钱包余额
/qshop reload                             从磁盘重新加载所有商店配置
/qshop currency list                      列出所有货币
/qshop currency create <id> <name> [color]
/qshop currency give|take|set <player> <currency> <amount>
/qshop shop create <id> [displayName] [currency]
/qshop edit <shop> add <type> [price] [currency]
/qshop edit <shop> remove <index>
/qshop edit <shop> setitem <index>
/qshop edit <shop> set <index> <field> <value>
/qshop item                              将你手持的物品打印为 Base64(用于配置文件)

/qshop edit add 的 type 可选值:buy、sell、barter、command。编辑命令需要权限等级 2。对于 /qshop shop create:省略货币时默认为 coins;名称中包含空格时请使用引号,例如 /qshop shop create vip "VIP Shop" tokens。

配置

  • 商店:world/serverconfig/qshop/shops/<id>.json — 每个商店一个文件,首次运行时自动生成(包含一个 starter 商店作为示例)。
  • 货币:world/serverconfig/qshop/currencies.json。
  • 服务器设置(world/serverconfig/qshop-server.toml):切换交易反馈消息并将其移至动作栏。
  • 客户端设置(config/qshop-client.toml):标签页列表的淡入淡出遮罩及其颜色。

配置文件中的物品格式(三种都可接受):"minecraft:diamond"、{"item": "minecraft:oak_log", "count": 8, "nbt": "{...}"} 或 Base64(模组保存的格式,也是 /qshop item 打印的格式)。

KubeJS 集成

安装 KubeJS 后,可以在 server_scripts 中使用 QShop 绑定。完整 API:

// ---- 商店 ----
QShop.open('starter', event.player);           // 为玩家打开(通过 ID 或 UUID)
QShop.openByUuid('xxxxxxxx-...', event.player);
QShop.exists('starter');                       // boolean
QShop.getShopIds();                            // string[]
QShop.getShopUuid('starter');
QShop.createShop('vip', 'VIP Shop', 'coins'); // 显示名称 + 默认货币(空字符串 = 'coins')
QShop.removeShop('vip');                       // boolean
QShop.reload();

// ---- 货币(钱包) ---- QShop.getBalance(event.player, 'coins'); // double QShop.giveCurrency(event.player, 'coins', 100); QShop.takeCurrency(event.player, 'coins', 10); QShop.setCurrency(event.player, 'points', 50); QShop.getCurrencies(); // string[] QShop.createCurrency('tokens', 'Tokens', '55ff55'); // boolean (十六进制颜色)

// ---- 子商店(标签页) ---- QShop.addTab('vip', 'Weapons', 'minecraft:iron_sword'); // 图标可选 QShop.addTab('vip', 'Armor', 'minecraft:diamond_chestplate', 'my-fixed-tab-uuid'); // uuid 可选,为空/省略 = 随机 QShop.updateTab('vip', 0, 'Armor', 'minecraft:diamond_chestplate'); // 按索引 QShop.updateTabByUuid('vip', tabUuid, 'Armor', null); // 按 uuid QShop.removeTab('vip', 0); QShop.removeTabByUuid('vip', tabUuid); // 保留至少一个标签页 QShop.getTabCount('vip'); QShop.getShopTabUuid('vip', 0);

// ---- 条目 ---- // 使用 JsonIO.of({...}) 构建条目 JSON(注意:是 JsonIO,不是 JsonUtils!) QShop.addEntry('vip', JsonIO.of({ type: 'SELL', // BUY | SELL | BARTER | COMMAND item: 'minecraft:diamond', price: 100, currency: 'coins', globalLimit: 100, playerLimit: 10, limitReset: 'DAILY', // NEVER | DAILY | WEEKLY | MONTHLY uuid: 'my-fixed-entry-uuid' // 可选,为空/省略 = 随机 })); QShop.addEntry('vip', 1, JsonIO.of({ type: 'BUY', item: {item: 'minecraft:oak_log', count: 8}, price: 2, currency: 'coins' })); // 2nd argument: Number = tab index (0-based), String = tab uuid QShop.addEntry('vip', tabUuid, JsonIO.of({ type: 'COMMAND', commands: [{command: 'give %player% diamond 1', op: true, silent: true}] })); QShop.updateEntry('vip', 0, JsonIO.of({ type: 'SELL', item: 'minecraft:netherite_ingot', price: 500, currency: 'coins' })); QShop.updateEntryByUuid('vip', tabUuid, entryUuid, JsonIO.of({ type: 'SELL', item: 'minecraft:emerald', price: 50 })); QShop.removeEntry('vip', 1, 0); // tab 1, index 0 QShop.removeEntryByUuid('vip', tabUuid, entryUuid); QShop.getEntryCount('vip'); // 默认标签页中的条目数 QShop.getEntryCount('vip', 1); QShop.getShopEntryUuid('vip', 0, 0);

// ---- 限制清理 ---- QShop.clearEntryLimits('vip', tabUuid, entryUuid); QShop.clearTabLimits('vip', tabUuid); QShop.clearShopLimits('vip');

流式构建器(JSON 形式的替代方案)

两种 API 功能等价且经过全面测试——使用你喜欢的那种即可。构建器更易于发现且逐步验证;JSON 形式则与商店配置文件保持一致。

// 交易条目
QShop.entry('vip')                          // 目标商店(第 2 个参数可选:Number = 标签页索引,String = 标签页 UUID)
    .sell('minecraft:diamond')               // 或 .buy(...) / .command() / .barter(give, receive)
    .price(100, 'coins')                     // 单价 + 货币
    .playerLimit(10, 'DAILY')                 // 玩家限制 + 重置周期 (NEVER/DAILY/WEEKLY/MONTHLY)
    .globalLimit(100)
    .description('§aRare material')
    .uuid('my-entry-id')                     // 可选,省略时随机
    .add();                                  // 返回 boolean

// 命令条目(cmd() 会自动将类型切换为 COMMAND) QShop.entry('vip') .cmd('give %player% minecraft:elytra 1', true, true) // 命令, op, silent .price(50, 'coins') .add();

// 易物,使用 JS 对象物品(也接受 {item, count, nbt}) QShop.entry('vip', 0) .barter({item: 'minecraft:stone', count: 2}, 'minecraft:cobblestone') .add();

// 子商店(标签页) QShop.tab('vip') .name('Weapons') .icon('minecraft:iron_sword') .uuid('my-tab-id') // 可选,省略时随机 .add();

构建器中的物品参数接受:物品 ID 字符串('minecraft:diamond')、ItemStack、物品 JSON 或 JS 对象({item, count, nbt})。

事件

// 购买前事件(可取消):在扣费/扣物之前触发
QShopEvents.beforeTrade(event => {
    console.log(event.playerName + ' 想买 ' + event.entryName + ' x' + event.units
            + ' 单价 ' + event.price + ' ' + event.currency);
    if (event.entryUuid === 'some-entry') {
        event.cancel();   // 取消这笔交易
        event.player.tell('该条目已下架');
    }
});

// 购买后事件(只读):成交后触发(含实际数量/实付/是否部分成交) QShopEvents.afterTrade(event => { console.log(event.playerName + ' 买了 ' + event.entryName + ' x' + event.tradedUnits + ',实付 ' + event.paidPrice + ' ' + event.currency + (event.partial ? ' (部分成交)' : '')); });

可用字段:player / playerName、shopId / shopUuid、tabIndex / entryIndex / entryUuid、entryType (BUY/SELL/BARTER/COMMAND)、entryName、price / currency、units(购买前)或 tradedUnits / totalItems / paidPrice / partial(购买后)。

实时同步

打开商店 GUI 的玩家会在 约 2 秒内收到修改过的条目/标签页推送 —— 如果另一位管理员或 KubeJS 脚本更改了商店(价格、条目、标签页),打开的 GUI 会自动刷新,同时保留滚动/标签页/编辑模式状态。

示例服务器脚本

// server_scripts/qshop_example.js
// 1) 一次性设置:在服务器完全启动后创建商店
let qshopSetupDone = false;
ServerEvents.tick(event => {
    if (qshopSetupDone) return;
    qshopSetupDone = true;
    if (!QShop.exists('vip')) {
        QShop.createShop('vip', 'VIP Shop', 'coins');
        QShop.addTab('vip', 'Weapons', 'minecraft:iron_sword');
        QShop.addEntry('vip', 0, JsonIO.of({
            type: 'SELL',
            item: 'minecraft:diamond',
            price: 100,
            currency: 'coins',
            playerLimit: 10,
            limitReset: 'DAILY'
        }));
        QShop.addEntry('vip', 0, JsonIO.of({
            type: 'COMMAND',
            commands: [{ command: 'give %player% minecraft:elytra 1', op: true, silent: true }]
        }));
    }
});

// 2) 欢迎奖励:每当玩家加入时 PlayerEvents.loggedIn(event => { QShop.giveCurrency(event.player, 'coins', 50); });

// 3) 打开商店的命令: /openshop ServerEvents.commandRegistry(event => { const { commands } = event; event.register( commands.literal('openshop') .requires(src => src.hasPermission(2)) .executes(ctx => { QShop.open('vip', ctx.source.entity); return 1; }) ); });

提示: 新创建的商店已经包含一个默认子商店,因此 QShop.getTabCount() 的初始值为 1,每次 QShop.addTab() 都会增加一个。建议在玩家触发的事件(PlayerEvents.*、命令、ServerEvents.tick)中进行商店设置——此时服务器的商店管理器已完全加载。

要求

QShop 没有强制性的模组依赖。 它可以在纯净的 Forge 1.20.1 服务器上开箱即用。KubeJS、FTB Quests 和 GameStages 都是 可选的,仅在你需要它们的具体功能时才需要。

  • 必需: Minecraft 1.20.1, Forge 47.x
  • 可选 — KubeJS: 脚本集成(QShop 绑定)。自动检测,仅在安装时加载。
  • 可选 — FTB Quests: 任务门槛。仅在为交易或标签页配置 requiredQuests 时需要。
  • 可选 — GameStages: 阶段门槛。仅在配置 requiredStages 时需要。如果安装了 KubeJS,阶段检查也可以通过 KubeJS 的玩家阶段进行(此时并非严格要求 GameStages),但传统的基于 GameStages 的门槛需要 GameStages 模组(例如 GameStages-Forge-1.20.1-15.0.2.jar)。

⚠️ 如果一笔交易配置了任务/阶段要求,但对应的模组缺失,则无法验证这些要求,该交易将被视为锁定。请在已安装 FTB Quests / GameStages(或 KubeJS)的服务器上保留要求配置。

权限

  • /qshop open(为其他玩家打开)、/qshop reload、/qshop currency、/qshop shop create、/qshop edit:权限等级 2(OP)
  • 游戏内商店编辑:权限等级 2 + 创造模式

许可证

ARR