VendingMachine

[!INFO] Git Commit: 4e6ec60 | Updated: 2026-08-22

GTNH(GregTech 5 - New Horizons)1.7.10 分支的自动售货机模组,作者 CubeFury, 源码包 com.cubefury.vendingmachine(注意是 com.cubefury,不是 cubefury),modid vendingmachine。

在 自动售货机 上放一堆物品 + 钱,GUI 里就能像商店一样购买。 交易目录来自 config/vendingmachine/tradeDatabase.json,货币是 DreamCraft 的 14 种任务硬币。 可选安装 ME 售货机上行接口 让机器直接从 AE2 网络取料。

⚠️ 硬依赖 GregTech:VendingMachine.init() 中 VendingMachineBlocks.registerBlocks() 与 VMItems.registerMultis() 都在 if (isGt5uNhLoaded) 里,isGt5uNhLoaded 由 Loader.isModLoaded("gregtech_nh") 决定。没有 gregtech_nh 时本 mod 的方块与物品一个都不注册, 只会留下 /vending 指令与配置文件。

硬性约束

MTEVendingMachine 的物品形态注册(VMItems.registerMultis())带 @Optional.Method(modid = "gregtech_nh"), 但调用点在运行时 if 判断里。GameRegistry.registerBlock / registerBlock 也全部经由 GregTech 的 GTGenericBlock 构造函数完成,因此本 mod 无法脱离 GregTech 独立运行。

方块列表

  • 自动售货机外壳 — 本 mod 唯一的 Block 子类,vendingmachine:casing,仅 metadata 0,硬度 5.0(铁块),扳手 / 等级 2
  • ME 售货机上行接口 — GTNH MTE 2742,Tier 3,AE2 网格代理,潜行扳手右键切换六面连接

物品列表

  • 占位物品 — vendingmachine:placeholder,交易库引用已删除 mod 的物品时的内部占位符,无法正常获得

多方块结构列表

  • 自动售货机 — 2×3×1 共 6 格,控制器 + 5 外壳,8 输入格 / 100 输出格

游戏设定列表

枚举一览(skill §2.14 穷举)

SortMode(blocks/gui/SortMode.java)— 穷举,共 2 个值

枚举值 lang key 图标 配置默认值
SMART vendingmachine.gui.display_sort_smart GuiTextures.SORT_SMART ✅ 默认
ALPHABET vendingmachine.gui.display_sort_alphabet GuiTextures.SORT_ALPHABET —

排序逻辑实际写在 TradeMainPanel.formatTrades() 的 Comparator 里,两者差异如下:

规则 SMART ALPHABET
收藏优先 ✅ ✅
可交易状态分组 ✅ getRank() 5 档 ❌
冷却剩余时间倒序 ✅ ❌
物品 ID / damage ✅ ❌
交易组序号 ✅ ❌
最终排序 上述依次 getDisplayName().compareTo(...) 纯字母序

getRank() 分档(SMART 专用,数字越小越靠前):

rank 条件
1 可交易且无冷却
2 可交易但在冷却中
3 不可交易且无冷却
4 不可交易且在冷却中
5 未启用(不满足 ICondition 条件)

DisplayType(blocks/gui/DisplayType.java)— 穷举,共 2 个值

枚举值 lang key 图标 布局 配置默认值
TILE vendingmachine.gui.display_mode_tile GuiTextures.MODE_TILE 每行 3 个(TILE_ITEMS_PER_ROW = 3),格 47×25 px ✅ 默认
LIST vendingmachine.gui.display_mode_list GuiTextures.MODE_LIST 每行 1 个(宽 153 × 高 14 px) —

两种布局的 300 个格子在客户端全部预先创建(displayedTradesTiles / displayedTradesList), 每 tick 用 setEnabledIf(display_type == displayType && display != null) 切换可见性。

WalletMode(blocks/gui/WalletMode.java)— 穷举,共 2 个值

枚举值 lang key 图标 配置默认值
PERSONAL vendingmachine.gui.display_wallet_personal GuiTextures.WALLET_PERSONAL ✅ 默认
TEAM vendingmachine.gui.display_wallet_team GuiTextures.WALLET_TEAM —

TEAM 模式下玩家没有团队时 TradeManager.getWallet() 返回 null,投币被静默丢弃。

CurrencyType(trade/CurrencyType.java)— 穷举,共 14 个值

全部为 DreamCraft 任务硬币。完整表格见 交易与定价系统。

TradeCategory(trade/TradeCategory.java)— 穷举,共 10 个值

FAVOURITES / ALL / COMPONENTS / RAW / FARMING / CHEMISTRY / MAGIC / BEES / MISC / UNKNOWN。 详见 交易与定价系统。

MusicTrack(blocks/gui/MusicTrack.java)— 穷举,共 2 个值

枚举值 lang key 音频 ResourceLocation 配置默认值
NONE vendingmachine.gui.display_track_none null —
LUNCH_BREAK vendingmachine.gui.display_track_lunch_break vendingmachine:track.lunch_break ✅ 默认

配置(VMConfig.java,GTNHLib @Config,文件 vendingmachine.cfg)

分组 字段 类型 默认值 需重启
vendingMachineSettings gui_refresh_interval int 20 需世界重启
vendingMachineSettings restock_notifications_enabled boolean true 需世界重启
vendingMachineSettings restock_notifications_interval int 200 需世界重启
gui display_type DisplayType TILE 否
gui sort_mode SortMode SMART 否
gui wallet_mode WalletMode PERSONAL 否
gui show_coins boolean true 否
gui update_coin_icon boolean false 否
team soloTeam boolean false 否
team maxTradeLimit int -1 否
music current_track MusicTrack LUNCH_BREAK 否
music music_volume float(范围 0~2) 0.75f 否
developer data_dir String vendingmachine 需 MC 重启
developer trade_db_dir String config/vendingmachine 需 MC 重启
developer force_rewrite_database boolean false 需世界重启

另有 @Config.Ignore 的 VMConfig.world_dir(File),由 SaveLoadHandler 在服务器启动时赋值,不写入配置文件。

team.maxTradeLimit = -1 表示「冷却期内允许的免费交易次数 = 当前在线队员数」, 详见 交易与定价系统。

GUI 说明

没有容器 GUI ID。MTEVendingMachine.forceUseMui2() 恒为 true,getGui() 直接 new MTEVendingMachineGui(this), 走 GregTech 的 ModularUI 2 面板系统(MTEMultiBlockBaseGui),本 mod 不含任何 Container / GuiContainer 子类, 所以不存在「GUI ID → Container」映射表。输出物品通过 fillPlayerInventoryWithDispensedItems() 直接塞玩家背包 (GUI 滑槽上 Shift + 左键)。

组件 源码 行为
分类标签 VendingPageButton 每个分类一个 PageButton,覆盖贴图 tradeCategories.get(index).getTexture();在 highlightedTabs 中时叠加 20×20 的 TAB_HIGHLIGHT;悬停时图标左右边距各增减 1px(ICON_MARGIN_LEFT = 7 / ICON_MARGIN_RIGHT = 5);点击时写 MTEVendingMachineGui.lastPage
分页 MUI2 PagedWidget 宽 162 px;高度 2 + clamp(rootPanel.height - 176, 1, 144),show_coins = false 时再 + COIN_DISPLAY_HEIGHT(55)。每个分类一个 ListWidget(宽 161),交易格子全部预建 300 个
搜索栏 SearchBar MUI2 BaseTextFieldWidget,占位文本 vendingmachine.gui.search。onUpdate 里文本变化即写静态字段 MTEVendingMachineGui.lastSearch、调 resetTradeDisplayScroll()、setForceRefresh()。搜索语法复用 NEI 的 SearchField.getFilter(...) + ItemFilter,匹配范围是「显示物品 + 所有产出 + 所有消耗」(TradeItemDisplay.satisfiesSearch)。搜索栏聚焦时吞掉所有 typedChar >= 32 且非 127 的按键
排序/筛选 TradeMainPanel 纯客户端(if (syncManager.isClient()))。按住 Shift 或 Ctrl 时停止重建列表,只更新已有格子的 enabled / hasCooldown / cooldownText / isFavourite 等状态
购买 / 收藏 TradeItemDisplayWidget onMousePressed 要求 shiftHeld != ctrlHeld(必须且只能按住 Shift 或只按住 Ctrl,两个都不按或同时按都 Result.IGNORE)。Shift + 左键 → attemptPurchase 购买;Ctrl + 左键 → FavouritesTracker.toggleFavourites(...) 收藏/取消收藏
退币/退料 ejectSingleCoin / ejectCoins / ejectItems 三个 ToggleButton 同步值。ejectCoins 把钱包里全部币种 itemize() 后退料并 resetAllCount;单币种开关只退该币种;ejectItems 把 8 个输入格物品搬进 outputBuffer 再清空输入格
音量 VolumeControlGui + volumeButton 独立面板,值写 VMConfig.music.music_volume(0~2)
音乐 VMMusicManager 打开 GUI 时 startVendingMachineMusic(true),onCloseAction 里 stopVendingMachineMusic()
使用者占用 NetResetVMUser TradeMainPanel.dispose() 时发包释放 currentUser

下落物品动画(fallingitem 包,已实际接线)

FallingItemSlotFactory 在 MTEVendingMachineGui.addAllItemSlotsAsChildren()(第 588 行)被 new FallingItemSlotFactory(base.outputItems, 18 * 4) 实例化,并为 全部 100 个输出格各生成一个 TransformWidget,不是死代码。

参数 值 说明
FALL_ANIMATION_DURATION 1000 ms 单次下落动画时长
MAX_X_POS 24 px 横向散布范围
落距 18 * 4 = 72 px 由 GUI 传入
插值曲线 Interpolation.BOUNCE_OUT,bounds(0, 1) 带回弹
X 分布 (int)((i * φ % 1.0) * 24),φ = (√5 - 1)/2 黄金比例共轭分配:每个新槽落在当前最大的空隙里,视觉上分布均匀但非线性
触发条件 槽位物品变化且 newItem != null && !onlyAmountChanged && !init 纯数量变化不重播动画

配套的 ItemSlotWithDepth 给物品图标加深度偏移,Pos 是可变的动画值对象(copyOrImmutable() 返回快照供 MutableObjectAnimator 终点使用)。 输出格全部调用了 ModularSlot.accessibility(false, true) 并归入 outputSlotGroup 槽位组, 同时 background(IDrawable.EMPTY) + disableHoverBackground(),即不画默认槽位背景与悬停高亮。

音效

音效 触发
vendingmachine:coin_drop 掉出的物品是货币(CurrencyItem.fromItemStack 非空)
vendingmachine:item_drop 掉出的物品是普通物品
vendingmachine:coin_insert 投币成功(insertCoin / depositHeldCoins)

附魔 / 耐久 / 配方

  • 本 mod 不注册任何合成配方。grep -rn "addRecipe\|addShapelessRecipe" 在 src/main/java 下只命中 NEI 的局部变量名, 真正的 GameRegistry 调用只有一行 registerItem(ItemPlaceholder.placeholder, "placeholder")。 售货机与上行接口的 GTNH 配方由 DreamCraft / 整合包提供,不在本仓库内。
  • 不注册任何附魔,占位物品也未覆写 getItemEnchantability(走 Item 默认)。
  • 外壳与两个机器均无耐久。

本 mod 没有的内容(grep 证据)

以下维度经全仓库 grep 确认完全没有实现,因此未建立对应目录。所有命令在 /Users/evlos/a/mirror/VendingMachine/src/main/java 下执行:

维度 grep 模式 结果
热键 grep -rn "KeyBinding|keyBinding|Lwjgl3Key|registerKeyBinding|InputEvent" . 0 命中 — 无任何 KeyBinding
附魔 grep -rn "Enchantment" . 0 命中
Buff / Debuff grep -rn "PotionEffect|MobEffect|registerPotion|applyPotionEffect" . 0 命中
群系 grep -rn "BiomeGen|BiomeProvider|registerBiome|getModifiableBiomes" . 0 命中
世界生成 grep -rn "IWorldGenerator|WorldGenerator|MapGenStructure|registerWorldGenerator" . 0 命中
成就 grep -rn "Achievement|addAchievement" . 0 命中
实体 grep -rn "extends Entity|registerEntity|EntityRegistry" . 0 命中 — 无任何 Entity 子类
容器 GUI grep -rn "extends Container|extends GuiContainer|ContainerGuiProvider|getContainer" . 0 命中 — GUI 走 ModularUI 2,无 GUI ID
投掷/药水类物品效果 grep -rn "extends ItemProjectile|onEntityItemUpdate|onItemUseFirst|onRightClick" . 0 命中
维度 grep -rn "WorldProvider|registerDimension" . 0 命中。仅 DimensionManager.getWorld(...) 出现在 4 个网络封包 handler 里,用于按维度 ID 取回 World 对象(NetResetVMUser / NetCoinDeposit / NetTradeRequestSync / PacketSender.sendToDimension),与「注册新维度」无关

补充说明:

  • 没有多方块家族:全仓库 grep -rln "MTEMultiBlockBase" 只命中 2 个文件 —— blocks/MTEVendingMachine.java(结构本体)和 blocks/gui/MTEVendingMachineGui.java(其 GUI)。 唯一的 IStructureDefinition 就在 MTEVendingMachine 里,没有第二个多方块结构。
  • 没有 structure/ 或 biome/ 源码目录:ls -d */ 在 src/main/java 下只有 com/ 一个目录。 STRUCTURE_DEFINITION 是玩家建造蓝图(survivalConstruct / construct),不是世界生成结构。
  • 没有独立 TileEntity 类:MTEVendingMachine / MTEVendingUplinkHatch 是 GregTech 的 IMetaTileEntity 实现,不是 net.minecraft.tileentity.TileEntity 子类;瓦片数据存在 GTNH 的 IGregTechTileEntity(NBT 键 inputs / outputs / outputBuffer / pendingInject)。
  • CfgElem(gui/client/VMGuiClientConfig.java)只有 15 行,只是把 VMConfig 包成 GTNHLib SimpleGuiConfig 的游戏内配置界面(super(parentScreen, MODID, NAME, true, VMConfig.class)), 自身不含任何配置字段;所有真实配置项都在 VMConfig 里。
  • 无 EntityPlayer 之外的权限检查:MTEVendingMachine.canUse() 只判断「有没有别人在用」, MTEVendingUplinkHatch.isAccessAllowed() 恒返回 true;唯一的权限等级在 /vending 指令上(等级 4)。
  • VMGuiClientConfig 构造时传入的 categorized = true,对应 VMConfig 里每个分组 (vendingMachineSettings / gui / team / music / developer)各自的 @Config.LangKey 分类标题。