Invsee 背包查看
基本信息
| 属性 | 值 |
|---|---|
| 指令 | /inv (CmdInv 注册时名为 view) |
| 指令源码 | src/main/java/serverutils/command/CmdInv.java:32-112 |
| 开关 | commands.inv(默认 true) |
| 权限 | Level.OP(requiredPermissionLevel = 2) |
| 别名 | edit(CmdInv.CmdView.getCommandAliases(),CmdInv.java:50-52) |
| 子命令 | 2(view、disable_right_click)+ 1 个 help |
| 注册类数 | 8(MainInventory + 7 个模组背包) |
| 注册接口 | serverutils.invsee.inventories.IModdedInventory |
| 容器 / GUI | InvseeContainer / GuiInvseeContainer |
子命令
CmdInv 构造函数(CmdInv.java:108-110)只挂 3 项:
| 子命令 | 类 | 权限 | 作用 |
|---|---|---|---|
/inv view <玩家> |
CmdInv.CmdView |
OP | 打开目标玩家的背包 GUI |
/inv disable_right_click <物品> |
CmdInv.CmdDisableRightClick |
OP | 把物品加入/移出禁用右键名单 |
/inv help |
CmdTreeHelp |
— | 自动生成 |
[!NOTE] 顶层
/inv本身不注册。ServerUtilitiesCommands.java:50注册的是new CmdInv(),而CmdInv继承CmdTreeBase并把所有功能放在子命令里。CmdInv构造函数的第 3 个参数是"view",即顶层/inv树的名字是view,直接输入/inv会进入子命令列表而不是执行查看。
CmdView.processCommand 用 checkArgs(sender, args, 1) 要求至少 1 个参数(目标玩家名),Tab 补全走 Universe.get().getPlayers() 的名字列表(CmdInv.java:38-43)。目标必须是 ForgePlayer,且不能是假玩家、必须有 NBT 数据(CmdInv.java:60)。
/inv disable_right_click 对应 world.disabled_right_click_items 配置项,该配置注释里明确写着「You can use ‘/inv disable_right_click’ command to do with from in-game.」(ServerUtilitiesConfig.java:592)。
IModdedInventory 接口
src/main/java/serverutils/invsee/inventories/IModdedInventory.java:16-44,7 个抽象方法 + 4 个 default:
| 方法 | 行 | 说明 |
|---|---|---|
loadOnlineInventory(EntityPlayerMP) |
17 | 在线玩家的背包 |
loadOfflineInventory(ForgePlayer) |
20 | 离线玩家的背包 |
createInventory(EntityPlayer, int size) |
23 | 构造可写容器 |
getButtonIcon() |
26 | 页签图标 |
getButtonText() |
29 | 页签文本(语言键) |
saveInventory(ForgePlayer, IInventory) |
31 | 写回 |
getSlot(player, inventory, index, x, y) |
34 | default:普通 Slot |
getSlotOverlay(Slot) |
38 | default:null |
getInventoryName() |
42 | default:回落到 getButtonText() |
getSlot / getSlotOverlay / getInventoryName 提供默认实现,使简单背包只需实现 7 个抽象方法。
8 个内置背包
InvSeeRegistry.DefaultInventories 私有枚举(invsee/inventories/InvSeeRegistry.java:24-48)在静态初始化块中按 mod 是否加载选择性实例化。
| 枚举 | modid | 类 | 语言键 |
|---|---|---|---|
MAIN |
— (无 modid,恒注册) | MainInventory |
serverutilities.invsee.main |
ENDER_CHEST |
— (无 modid,恒注册) | EnderInventory |
serverutilities.invsee.ender_chest |
ADVENTURE_BACKPACK |
adventurebackpack |
AdventureBackpackInv |
serverutilities.invsee.adventure_backpack |
BAUBLES |
Baubles |
BaublesInventory |
serverutilities.invsee.baubles |
BATTLE_GEAR |
battlegear2 |
BattlegearInventory |
serverutilities.invsee.battlegear |
BACKPACK |
Backpack |
MinecraftBackpackInv |
serverutilities.invsee.personal_backpack |
GALACTICRAFT |
GalacticraftCore |
GalacticraftInventory |
serverutilities.invsee.galacticraft |
TINKERS_CONSTRUCT |
TConstruct |
TiCInventory |
serverutilities.invsee.tic_bag |
MAIN 与 ENDER_CHEST 的 modid 为 null,条件 (modId == null || Loader.isModLoaded(modId)) 恒真 —— 主背包与末影箱永远存在,其余 6 个按 mod 是否加载决定。
无条件存在:2 个(主背包、末影箱);条件注册:6 个。
[!NOTE] modid 与项目名不完全一致:
BACKPACK用的 modid 是Backpack(对应 Minecraft Backpack Mod),GALACTICRAFT用的是GalacticraftCore而非Galacticraft,TINKERS_CONSTRUCT用的是TConstruct。这些字符串直接来自源码,未做推断。
双重加载判定
BATTLE_GEAR 是唯一带第三个判定条件的条目(InvSeeRegistry.java:32):
BATTLE_GEAR("battlegear2", BattlegearInventory.class,
() -> CommonUtils.getClassExists("mods.battlegear2.api.core.IInventoryPlayerBattle"))
除了 Loader.isModLoaded("battlegear2"),还用 CommonUtils.getClassExists(...) 反射确认 Battlegear2 的内部 API 类存在 —— 防止 mod 存在但版本不兼容时类加载失败。
BaublesInventory 内部另有一层判定(:30):
private static final boolean isExpanded = CommonUtils.getClassExists("baubles.api.expanded.BaubleExpandedSlots");
用于在 Baubles-Expanded 与旧版 Baubles 之间切换(该类 import 的是 baubles.api.expanded.BaubleExpandedSlots)。
槽位定制
3 个实现覆写了 getSlot,因为它们的槽位不是线性排列:
| 类 | 定制方式 | 行 |
|---|---|---|
MainInventory |
护甲槽用 SlotArmor,索引需换算:getArmorSlotIndex(index, inventory) + SlotArmor(inventory, armorSlot, x, y, player, size-1-armorSlot) |
:70-83 |
BattlegearInventory |
双列武器槽:OFFSET + index / 2,WEAPON_SETS 偏移,纵向位置 y - 36 + (index/2) * 18 |
:63-72 |
GalacticraftInventory |
switch(index):0 → y=17,1 → y=35,其余走 SlotExtendedInventory |
:89-92 |
MinecraftBackpackInv |
全部用 SlotBackpack |
:55-56 |
TiCInventory |
饰品槽用匿名 SlotAccessory 子类,index < 4 时 maxStackSize=1,否则 10 |
:103-107 |
GalacticraftInventory 是唯一覆写 getInventoryName() 的实现(:83-86),页签显示 serverutilities.invsee.galacticraft 但容器标题显示 serverutilities.invsee.gc_items。
注册机制
// InvSeeRegistry.java:20-22
static {
// Initializes all default inventories
DefaultInventories.MAIN.ordinal();
}
触发静态初始化只需访问 MAIN.ordinal(),返回值被丢弃(源码注释 // noinspection ResultOfMethodCallIgnored 也承认了这点)。副作用是整段枚举构造逻辑被执行。
外部模组可调 InvSeeRegistry.registerInventory(IModdedInventory)(:48)追加,模组自身也暴露了转发入口 ServerUtilitiesRegistry.registerInvseeInventory(...)(ServerUtilitiesRegistry.java:94)。
getMainInventory()(:56-58)实现为 registeredInventories.get(0) 后强转 IModdedInventory —— 它依赖 MAIN 永远是第一个被注册这一隐式约定,若外部模组在类初始化前抢先注册,列表顺序可能变化。
静默失败
DefaultInventories 构造函数的注册调用带空 catch(:46):
try {
registerInventory(inventory.getDeclaredConstructor().newInstance());
} catch (Exception ignored) {}
任何反射失败(构造器不存在、类版本不兼容、NoClassDefFoundError 被 Exception 之外的路径捕获等)都会被完全吞掉 —— 对应的 invsee 页签静默消失,控制台无任何输出。管理员只能通过页签数量反推某个 mod 的背包是否集成成功。