跳到主要内容

Shops

商店菜单,支持固定、动态或以物易物的价格、库存、限购、税费和三种货币类型。

免费v1.12经济与制作
依赖TypeWriter0.9GUI0.17+Vault可选PlaceholderAPI可选Discord可选

概览​

Shops 让玩家通过你在 TypeWriter 网页面板中构建的物品栏菜单买卖物品。一个商店就是一个 Shop Definition 条目:它包含在售物品列表、价格、货币、限制、消息以及菜单布局。玩家可通过 /tw shop <id> 打开它,也可以通过由任务、NPC 或其他菜单的按钮触发的 Open Shop 动作打开。

每件物品的定价方式有三种:固定价格、随库存变化的动态价格,或者以物易物,即用其他物品而非金钱来支付。购买和出售可通过 Vault、PlaceholderAPI 经济或内置的点数余额进行。

菜单本身由 GUI 扩展绘制。你在布局中放置用于显示商品的标记(SHOP_ITEM),以及用于翻页、关闭和选择数量的带标签按钮,Shops 会为每位玩家填充它们。

功能​

  • 固定价格、动态价格和物品价格 — 每件的统一价格、随库存减少而上涨的价格,或需要交出的物品列表。
  • 以物易物 — priceMode 为 ITEMS 时,以一种或多种物品交换一件商品,支持全店默认价格和 {item_price} lore 令牌。
  • 三种货币 — VAULT、PLACEHOLDER(从 PlaceholderAPI 占位符读取余额,并通过控制台命令修改)以及内置的 POINTS 余额。
  • 点击动作与数量 — 买卖 1 个、一组、最大数量、全部、通过对话框输入的自定义数量,或者在子菜单中买卖固定数量(例如 10 个)。
  • 库存、限制与重置 — 每件物品的库存、按玩家和全局的限购、全店买卖上限,以及按 cron、间隔、每日、每周或每月计划进行的重置。
  • 税费与促销 — 全局税率、按物品覆盖的税率,以及限时的百分比折扣。
  • 面板中的菜单 — 布局池、子菜单、填充物品、价格 lore,以及默认的名称和 lore 格式。
  • 共享菜单的标签页 — 通过 baseMenuId,商店可以成为更大的 GUI 底盘的一个视图。
  • 管理员命令 — 列出、查看、打开、重置、编辑库存以及读取交易记录。
  • Discord 公告 — 交易会作为事件公布,由 Discord 扩展转发到 webhook。

安装​

  1. 在 Paper 或 Folia 服务器上安装 GUI 扩展(0.17 或更新版本)和 TypeWriter。
  2. 可选,用于 VAULT 货币:安装 Vault 以及一个向其注册的经济插件。
  3. 可选,用于标题和 lore 中的占位符,以及 PLACEHOLDER 货币:安装 PlaceholderAPI。
  4. 从 GitHub Releases(上方摘要面板中的按钮)下载 Shops 的 JAR,放入 plugins/Typewriter/extensions/ 并重启服务器。
  5. 在网页面板中,创建一个 Shop Artifact 条目,并将其命名为 shop_artifact。Shops 通过该条目读写其库存、限制和点数。

快速上手​

构建一个用 Vault 金钱支付的小型武器商店:

  1. 在一个**静态(static)**页面上,添加一个名为 shop_artifact 的 Shop Artifact 条目。
  2. 添加一个 Shop Definition 条目。将 title 设为 <gold>Weapons,rows 设为 3,currency 设为 VAULT。
  3. 在 items 中添加一个物品。在 item 中选择钻石剑,将 name 设为 <aqua>Sword,priceMode 设为 FIXED,fixedBuyPrice 设为 500,fixedSellPrice 设为 100。将 primaryAction 保持为 BUY_1,secondaryAction 保持为 SELL_1。
  4. 在 layoutPool 中,添加一个 id 为 main 的 simple 布局,并在 mainLayoutId 中填入 main。向其中添加一个物品,buttonPrefix 为 shop_button:,buttonType 为 SHOP_ITEM,x 为 1,y 为 1,count 为 7,direction 为 right。这一行标记就是商品出现的位置。
  5. 再向布局中添加三个物品,buttonPrefix 为 shop_button:,buttonType 分别设为 PREV_PAGE、NEXT_PAGE 和 CLOSE,放在最下面一行。为每个物品设置 item 和 displayName。
  6. 将 fillerItem 设为灰色染色玻璃板,以填充未使用的槽位。
  7. 发布,然后在游戏中运行 /tw shop admin list 查看商店 id,并用 /tw shop <id> 打开它。若想改为从任务或 NPC 打开,请添加一个指向该商店的 Open Shop 动作。

左键购买一把剑,右键出售一把,如果你配置了购买子菜单,Shift + 左键会打开它。

显示的物品与出售的物品

商店每页显示的商品数量等于它拥有的 SHOP_ITEM 标记槽位数,其余商品通过翻页按钮浏览。如果不存在任何标记,菜单打开时不会有任何商品。

工作原理​

打开时,商店会检查其访问 criteria,应用待处理的重置,然后为该玩家构建菜单。点击会把动作发送给交易处理器,由它检查限制、结合促销和税费计算价格、扣除付款、交付物品,并记录库存、限购计数和历史记录。

菜单​

布局与按钮​

商店菜单是 Shop Definition 的 layoutPool 中的一个 simple 布局,由 mainLayoutId 选定。布局中的物品带有 shop_button: 的 buttonPrefix 和一个 buttonType:

buttonType作用
SHOP_ITEM放置商品的槽位。可使用 count、direction、gap 和 repeatY 重复。对于该标记,前缀可以留空。
NEXT_PAGE、PREV_PAGE在商品页面之间切换。
INFO发送 infoMessage 的各行。
CLOSE关闭菜单。
BACK返回上一个菜单,例如从子菜单返回。
BUY_1、SELL_1买入或卖出一个单位。在子菜单中,BUY_1:10 和 SELL_1:10 会交易 10 个单位;任意数字均可。
BUY_STACK、SELL_STACK交易一整组该物品。
BUY_MAX、SELL_ALL尽可能多地购买,或出售所有匹配的物品。
BUY_CUSTOM、SELL_CUSTOM通过对话框询问玩家数量。
BUY_SUBMENU、SELL_SUBMENU打开该物品的子菜单。

除 SHOP_ITEM、NEXT_PAGE、PREV_PAGE、INFO、CLOSE 和 BACK 之外的按钮都作用于玩家所选的物品,因此它们应放在子菜单布局中。请始终将 buttonPrefix 设为 shop_button::默认前缀属于另一个扩展,按钮会保持无反应。

子菜单​

在布局池中创建第二个 simple 布局,把数量按钮放进去,并在 subMenuLayoutId 中填入它的 id。标题来自 subMenuTitle,其中 {item_name} 是该物品的 name。物品通过 BUY_SUBMENU 和 SELL_SUBMENU 动作打开它,这是默认的 Shift 点击。

点击动作​

每个物品有三个动作槽位:

字段默认值默认点击方式
primaryActionBUY_1左键
secondaryActionSELL_1右键
shiftActionBUY_SUBMENUShift + 左键

可用的动作有 NONE、BUY_1、SELL_1、BUY_STACK、SELL_STACK、BUY_MAX、SELL_ALL、BUY_SUBMENU、SELL_SUBMENU、BUY_CUSTOM 和 SELL_CUSTOM。全店的 interactionConfig 决定哪种点击触发哪个动作(买 1 个、卖 1 个、买一组、全部卖出、子菜单、自定义数量、导航)。每种点击类型对每个物品只能运行一个动作,因此在默认配置下,映射到左键的多个动作会相互替换:如果你使用三个以上的动作,请修改 interactionConfig。

buyMaxEnabled 可为某个物品关闭 BUY_MAX。

数量对话框​

BUY_CUSTOM 和 SELL_CUSTOM 会打开一个带有一个文本字段的对话框。可通过 buyAmountPrompt、sellAmountPrompt、amountConfirmButton、amountInputLabel、amountInputPlaceholder、amountInputWidth 和 amountInputMaxLength 进行自定义。输入必须是正整数;否则会发送 invalidAmountMessage。确认数量时,会运行常规的价格、限制和余额检查。

物品显示​

priceLore 的各行会追加到每个商品上,位于物品自身的 lore 及其 lore 之后。对于没有自己的 name 或 lore 的物品,由 defaultNameFormat 和 defaultLoreFormat 补全。在这些字段以及物品的 name 和 lore 中,以下令牌会被替换;请求不存在的价格的行会被丢弃。

令牌替换为
{item}物品自身的名称。
{buy}、{sell}格式化后的单价。当物品无法买入或卖出、价格为零,以及对于以物易物的物品时,会被丢弃。
{item_price}以物品表示的价格,每个所需物品一行,例如 - 12 × Copper Ingot(措辞由 itemPriceLineFormat 设置)。对于不以物品支付的物品会被丢弃。
{stock}、{stock_max}当前库存和最大库存。
{currency}currencySymbol。

priceFormat 决定价格的显示形式:{amount} 是保留两位小数的数字,{currency} 是符号。

价格与经济​

价格模式。 FIXED 使用 fixedBuyPrice 和 fixedSellPrice。DYNAMIC 使用 dynamicPricing:库存充足时价格从 price(出售时为 sellPrice)开始,随着库存减少向 max 移动,步长为 stockMax 的 step,受 min 和 max 限制,并按 rounding 取整。ITEMS 是以物易物。

以物易物。 当 priceMode 为 ITEMS 时,在物品的 itemPrice 中列出费用,或在 defaultItemPrice 中为整个商店统一设置一次。每一行是一个物品和每购买一个单位所需的 amount。多行需要同时满足,购买多个单位时每一行都会相应倍增。某件商品自己的列表会替换商店的列表,而不会叠加。任何地方都没有价格的物品会被拒绝并记录日志,绝不会白送。在移除任何东西之前,会针对整个物品栏检查付款,并首先检查是否有空间容纳所购物品,目录物品是按标识而非仅按材质匹配的。BUY_MAX 会购买物品栏能够支付的最大数量。以物易物的物品无法折换成金钱卖回。缺少的物品会通过 cannotAffordItemsMessage 报告,其中 {missing} 列出它们,{amount} 是数量。

货币。

currency行为
VAULT使用在 Vault 中注册的经济。需要 Vault 插件和一个提供者。
PLACEHOLDER从 balancePlaceholder 读取余额,并通过从控制台运行 addCommand 和 removeCommand 来修改它,其中 {player} 和 {amount} 会被替换。三者都必须设置。可识别 1,250.5 和 12.5k 这样的数字。
POINTS由 Shops 在其构件中保存的余额。它从 0 开始,并在玩家出售物品时增长;该版本没有发放点数的命令。

税费。 taxRate 是一个百分比,在购买时加收,在出售时扣除。当 taxMode 为 PER_ITEM 时,物品的 taxRateOverride(0 或更大)会为该物品替换它;-1 表示沿用商店税率。

促销。 Shop Promotion 会按百分比降低某个商店的购买价和出售奖励,适用于所有物品或所列出的 targetItemIndices(在 items 中的位置,从 0 开始)。

库存、限制与重置​

  • dynamicPricing.stockMax 设置物品的库存。库存从满开始,玩家购买时减少,出售时增加。0 表示不追踪库存。启用 perPlayer 后,每位玩家对每件物品都有自己的库存;重置会补满所有人的库存。商店打开期间,defaultRefreshTicks(以及物品的 refreshTicks)会让商店在显示的库存变化时重新绘制,因此能看到其他玩家的购买。物品的 guiSlot 会把它固定在指定的格子上,sellAllEnabled 会关闭它的“全部出售”动作。详情见 Shop Definition。
  • playerLimit 和 globalLimit 限制每个重置周期内,单个玩家或所有人合计可购买某件物品的数量。globalPlayerLimit、globalBuyLimit 和 globalSellLimit 限制整个商店;0 表示不限。达到上限时会发送 limitReachedMessage。
  • reset 会恢复库存并清除限购计数。策略有 none、cron(例如 0 0 * * * 这样的表达式)、interval(秒)、daily、weekly(第 1 天为星期一)和 monthly。时间使用服务器时区。每个周期的第一次检查发生在商店被打开时,并且每分钟也会运行一次检查。
  • criteria 限制对商店的访问,不匹配时会发送 criteriaFailMessage。自身 criteria 不匹配的物品会显示为 lockedItem,且无法购买。

命令​

/typewriter 的子命令,通常输入为 /tw。

命令说明
/tw shop <shop>为自己打开一个商店。需要 typewriter.shop.open。
/tw shop admin列出管理员命令。需要 typewriter.shop.admin。
/tw shop admin list列出已加载的商店及其 id、名称、物品数量和池数量。
/tw shop admin info <shop>显示商店定义及其物品和索引。
/tw shop admin open <shop> [target]为某位玩家打开商店。
/tw shop admin refresh [target]重绘某位玩家已打开的商店。
/tw shop admin close [target]关闭某位玩家已打开的商店。
/tw shop admin reset <shop>立即重置库存和限制。
/tw shop admin stock <shop> <item> get显示该索引处物品的库存。
/tw shop admin stock <shop> <item> set <amount>设置库存。另有 add 和 remove。
/tw shop admin history [target]显示某位玩家自上次重启以来的最近 10 笔交易。

<shop> 是条目 id,可用 Tab 键补全。<item> 是物品在商店 items 中的位置,从 0 开始。

可编辑文本​

游戏中显示的文本没有任何一条固定在代码里。以下三类文本无需重新构建即可修改。

玩家在交易时读到的文本是 Shop Definition 的字段:purchaseMessage、sellMessage、cannotAffordMessage、limitReachedMessage、priceLore,以及上文介绍的其他字段。

价格措辞、趋势图标和池的行是各自所属条目的字段。默认值就是扩展一直显示的文本。

字段条目默认值用途
trendUpIconShop Definition<red>▲</red>自上次购买或出售以来价格上涨的物品的 trend_icon 占位符。
trendDownIconShop Definition<green>▼</green>价格下跌时的同一占位符。
trendStableIconShop Definition<gray>●</gray>价格没有变动或不知道之前价格时的同一占位符。
itemPriceLineFormatShop Definition- {amount} × {name}{item_price} Lore 令牌中的一行,对应以物品计价所要求的每个物品。{amount} 为数量,{name} 为物品名称。
unknownItemNameShop Definitionunknown item无法读取自身名称的价格物品的名称,用于 {item_price} 和缺少物品列表。
missingItemFormatShop Definition{amount} × {name}cannotAffordItemsMessage 的 {missing} 列表中的一项。{amount} 为缺少的数量。
missingItemSeparatorShop Definition, {missing} 列表中两项之间的内容。
defaultItemNameShop DefinitionItem对于没有 name 的物品,subMenuTitle 中的 {item_name}。
noPriceTextShop Definition—在以物品计价而没有数字的地方显示,用于 buy_price 和 sell_price 占位符,以及此类物品的背包已满消息中的 {price}。
cooldownFormatShop Definition{d}d:{h}h:{m}m:{s}s占位符未指定格式时 cooldown 占位符使用的模式。{d} 为天数,{h}、{m}、{s} 补足 2 位数字。
limitedLoreShop Pool<yellow>Limited: <red>{limit}池中限量物品的最后一行 Lore。{limit} 为可用数量。
unlimitedLoreShop Pool<yellow>Unlimited池中无限制物品的最后一行 Lore。

在物品的 Lore 中使用 %typewriter_shop:<shop>:<n>:trend_icon% 即可显示图标。%typewriter_shop:<shop>:<n>:trend% 返回 up、down、stable 或 unknown,这四个词是固定的,因为条件会与它们比较。

/tw shop 命令的回复(包括 /tw shop admin 命令)是 TypeWriter 的 snippet。打开 plugins/Typewriter/snippets.yml,找到 content.shop 下的键,修改值并运行 /tw reload。扩展在启动时会以默认文本注册其所有 snippet,因此首次启动后它们会出现在该文件中。保持默认值则不会有任何变化。

回复使用 MiniMessage,发送时不带 Typewriter 前缀,因此回复的颜色属于其文本的一部分。文本中的 {name} 会被表中所列的值替换;不存在的占位符会原样保留,值中不会再次查找占位符。来自你页面的值(商店名称、玩家名称、背包标题)按原样显示,其标签不会被解析。服务器控制台日志保持英文。

Snippet(位于 content.shop. 下)默认值占位符
help<gold>Shop commands:</gold> <yellow>/tw shop <id></yellow> to open a shop, <yellow>/tw shop admin</yellow> for operator tools.无
unknown_shop_errorUnknown shop: {shop} (纯文本){shop} (输入的内容)
admin.help管理命令列表,每行一条无
admin.unknown_shop<red>Unknown shop: {shop}{shop}
admin.list_empty<yellow>No shop definitions are currently loaded.无
admin.list_header<gold>Loaded shops ({count}):{count}
admin.list_line<white>- {id} | {name} | items={items} | pools={pools}{id}, {name}, {items}, {pools}
admin.opened<green>Opened {shop} for {player}.{shop}, {player}
admin.refreshed<green>Refreshed the shop session for {player}.{player}
admin.closed<green>Closed the shop session for {player}.{player}
admin.reset<green>Reset runtime stock and limits for {shop}.{shop}
admin.history_header<gold>Recent transactions for {player} ({count}):{player}, {count}
admin.history_empty<yellow>No transactions recorded in the current runtime.无
admin.history_line<white>{time} | {type} | {shop}[{index}] | amount={amount} | price={price}{time}, {type}, {shop}, {index}, {amount}, {price}
admin.info_title<gold>=== Shop {shop} ==={shop}
admin.info_name<white>Name: {name}{name}
admin.info_inventory_title<white>Title: {title}{title}
admin.info_currency<white>Currency: {currency} | rows={rows}{currency}, {rows}
admin.info_items<white>Items: {items} direct | pools={pools} | access criteria={criteria}{items}, {pools}, {criteria}
admin.info_reset<white>Reset policy: {policy}{policy}
admin.info_item<white>[{index}] {name} | buy={buy} sell={sell} | playerLimit={playerLimit} globalLimit={globalLimit} | item criteria={criteria}{index}, {name}, {buy}, {sell}, {playerLimit}, {globalLimit}, {criteria}
admin.info_item_unnamed<unnamed> (纯文本)无;它是没有名称的物品的 {name}
admin.stock_unlimited<yellow>{shop}[{index}] uses unlimited stock.{shop}, {index}
admin.stock_value<green>{shop}[{index}] stock: {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.stock_out_of_range<red>Stock must stay between 0 and {max}.{max}
admin.stock_updated<green>{shop}[{index}] stock set to {stock}/{max}.{shop}, {index}, {stock}, {max}
admin.unknown_item<red>Unknown direct item index {index} in {shop}.{index}, {shop}

unknown_shop_error 是命令错误,玩家输入时由游戏以红色显示:它是纯文本,不使用 MiniMessage。

权限​

权限授予
typewriter.shop.open/tw shop <shop>。默认授予所有人。
typewriter.shop.admin所有 /tw shop admin 命令。默认仅管理员拥有。

占位符​

PlaceholderAPI 占位符,使用 typewriter 扩展。<shop> 是商店条目的名称或 id,<n> 是物品在 items 中的位置,从 0 开始。

占位符返回
%typewriter_shop:<shop>:name%条目的名称。
%typewriter_shop:<shop>:entryid%条目的 id。
%typewriter_shop:<shop>:cooldown%距离下次重置的时间,默认格式为 0d:00h:00m:00s(商店的 cooldownFormat 字段;早期版本用 j 表示天)。可在下划线后添加格式,例如 cooldown_{h}h{m}m,可使用 {d}、{h}、{m} 和 {s}。
%typewriter_shop:<shop>:<n>:buy_price%物品的购买价格;以物易物的物品显示商店的 noPriceText(默认为 —)。
%typewriter_shop:<shop>:<n>:sell_price%物品的出售价格;以物易物的物品显示商店的 noPriceText(默认为 —)。
%typewriter_shop:<shop>:<n>:stock%当前库存。
%typewriter_shop:<shop>:<n>:stock_max%最大库存。
%typewriter_shop:<shop>:<n>:has_promotion%当有促销适用于该物品时为 true。

集成​

  • GUI — Shops 通过 GUI 会话服务打开其菜单,因此 gui:back、音效、扩展底盘和 baseMenuId 的行为与其他任何菜单相同。将 baseMenuId 设为一个共享的 Open GUI 外壳并添加 views,商店就会成为一个标签页:contentX、contentY、contentColumns 和 contentRows 用于在外壳内放置商品。
  • Discord — 每次购买或出售后,Shops 都会触发一个 ShopTransactionEvent。当安装了 Discord 扩展且商店设置了 notificationWebhookId 时,一个 Shop Notification 条目会格式化该公告。
  • 音效 — buySound、sellSound 和 deniedSound 会在相应的结果发生时播放。

条目​

6 个条目,由扩展源码生成。

动作1
构件1
清单4

故障排查​

菜单打开了,但没有显示任何商品。 布局中没有 SHOP_ITEM 标记,mainLayoutId 与 layoutPool 中任何布局的 id 都不匹配,或者该布局不是 simple 布局。商品只会出现在标记槽位上。

某个按钮没有任何作用。 它的 buttonPrefix 不是 shop_button:,或者它的 buttonType 拼写有误(类型为大写)。翻页或关闭按钮同样需要该前缀;只有 SHOP_ITEM 允许前缀为空。

商店提示 “No economy available”。 使用 VAULT 时,缺少 Vault 或其经济提供者。使用 PLACEHOLDER 时,balancePlaceholder、addCommand 或 removeCommand 之一为空。对于以物易物的物品,当没有设置 itemPrice 和 defaultItemPrice 时也会出现该消息,并且控制台会记录商店 id 和物品编号。

重启后库存和限制丢失,或从未被保存。 必须存在一个 id 或名称为 shop_artifact 的 Shop Artifact 条目。

某个物品无法出售。 它的卖出价为零(以物易物的物品始终如此),sellEnabled 已关闭,或者玩家持有的匹配物品不足。只有物品类型和数据完全一致的物品才会被计入。

控制台警告某个物品无法显示或已被移动。 带有 guiSlot 的物品会占用一个 SHOP_ITEM 标记。当被固定的物品占满所有标记时,其余物品会放到内容区域的空闲格子上;如果没有空闲格子,该物品将不会显示。请添加 SHOP_ITEM 标记,或空出一个格子。

itemPools 中的物品显示出来了但无法购买。 在此版本中,买卖动作只处理 items 中的条目;池中的物品显示在它们之后。

限制始终不会重置。 reset 为 none。请选择一个策略,或使用 /tw shop admin reset <shop>。

/tw shop <id> 提示该商店未知。 该命令接受的是条目 id,而不是标题。请使用 /tw shop admin list。

相关扩展​

  • GUI — 商店菜单所使用的布局、会话和标记机制。
  • Discord — 通过 webhook 公布商店交易。
  • Quest Codex — 在任务菜单中使用相同的底盘理念。