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 的背包是否集成成功。

相关条目