Packed Inventory

Packed Inventory

库存管理从未如此简单。

基础库

Logo

紧凑背包

GitHub 构建状态 版本 Modrinth CurseForge 许可证

一个 Minecraft 模组,旨在通过直接在你的背包中管理潜影盒、末影箱等物品的内容,来缓解日益严重的背包问题。

哦,我们还为潜影盒、末影箱、地图等提供了精美的悬浮提示!


特性

  • 无需放置潜影盒即可管理其背包。
  • 无需放置末影箱即可管理你的末影箱背包。
  • 快速将物品移入或移出潜影盒、末影箱及其他提供背包的物品。
  • 直接从你的背包中使用工作台、切石机等工作站。
  • 为潜影盒提供原版风格的悬浮提示。
  • 为末影箱提供原版风格的悬浮提示。
  • 为已填充的地图提供原版风格的悬浮提示。
  • 为在创造模式下通过 Ctrl + 鼠标中键 获得的包含 NBT 的物品提供原版风格的悬浮提示,例如箱子、木桶、熔炉、漏斗、发射器、投掷器等。
  • 一个广泛而简单的 API,帮助其他模组作者为其方块和物品实现这些功能。

类似收纳袋的功能

为背包物品提供的类似收纳袋的功能

如前所述,提供背包的物品 (例如潜影盒、末影箱,以及任何通过 Packed Inventory API 注册的物品) 会自动获得类似收纳袋的功能,允许你快速将物品移入或移出它们。

要将物品存入提供背包的物品中,你可以:

  • 拾取该提供背包的物品,然后按下 k 键点击要存放的物品,或
  • 拾取该提供背包的物品,然后按下 k 键点击要存放的提供背包的物品,或
  • 拾取要存放的物品,然后按下 k 键点击提供背包的物品。

要从提供背包的物品中取出物品:

  • 拾取该提供背包的物品,然后按下 k 键点击空槽位以取出物品,或
  • 拾取该提供背包的物品,然后按下 lctrl + k 键点击槽位以取出物品,或
  • 按下 lctrl + k 键点击提供背包的物品以取出物品。

要将提供背包的物品中的全部物品倒出到世界中:

  • 拾取该提供背包的物品,将其移动到背包界面之外,然后按下 k 键。

悬浮提示

以下是一些悬浮提示示例:

  • 容器提示
  • 彩色容器提示
  • 紧凑容器提示
  • 已填充地图提示

预览

更多截图可以在 这里 找到。


按键绑定

按键绑定可以像原版一样配置:

  1. 进入 选项
  2. 选择 控制
  3. 然后选择 按键绑定
  4. 向下滚动到 Packed Inventory 部分。
  5. 配置可用的按键绑定。
名称 描述 默认
与物品交互 与当前快捷栏槽位中的物品交互。或者,如果背包界面已打开,则与鼠标悬停的物品交互。根据上下文,此操作可以:
  • 打开与该物品关联的界面,
  • 将物品存入选定的提供背包的物品中,
  • 从选定的提供背包的物品中取出物品,或
  • 将选定的提供背包的物品中存储的所有物品倒出到世界中
k
切换交互模式(按住) 使 与物品交互 按键绑定行为如同 从物品中提取 Left Control
从物品中提取 从选定的提供背包的物品中提取一个物品 l
反转提示可见性(按住) 按下时反转提示可见性 Left Shift
反转提示紧凑模式(按住) 按下时反转提示紧凑模式 c

请注意,这些按键绑定与其他按键冲突是完全正常的,因为它们仅在你的背包界面打开时生效,而其他按键绑定通常在该界面下无效。


配置

如果你安装了 Cloth Config,你可以自定义本模组的行为。配置文件通常位于 ./config/packed-inventory.json,默认内容如下:

{
  "defaultTooltipConfig": {
    "showWhenEmpty": false,
    "rows": -1,
    "columns": -1,
    "usePredefinedColor": false,
    "enable": true,
    "compact": false
  },
  "tooltips": {
    "minecraft:shulker_box": {
      "showWhenEmpty": false,
      "rows": -1,
      "columns": -1,
      "usePredefinedColor": false,
      "enable": true,
      "compact": false
    },
    "minecraft:ender_chest": {
      "syncInterval": 5000,
      "showWhenEmpty": false,
      "rows": -1,
      "columns": -1,
      "usePredefinedColor": false,
      "enable": true,
      "compact": false
    },
    "minecraft:filled_map": {
      "size": 128,
      "enable": false,
      "compact": false
    }
  },
  "defaultValidationConfig": {
    "suppressValidationInCreative": true,
    "requiresPlayerOnGround": true,
    "enable": true
  },
  "validation": {
    "minecraft:shulker_box": {
      "suppressValidationInCreative": true,
      "requiresPlayerOnGround": true,
      "enable": true
    },
    "minecraft:ender_chest": {
      "requiresSilkTouch": true,
      "suppressValidationInCreative": true,
      "requiresPlayerOnGround": true,
      "enable": true
    }
  }
}
名称 描述 客户端/服务器 默认值
tooltip.enable 指示此提示是否默认启用 客户端 true
tooltip.compact 指示此提示是否默认启用紧凑模式 客户端 false
tooltip.showWhenEmpty 指示当内容为空时是否应显示此提示 客户端 false
tooltip.rows 指定用于显示提示内容所需的行数 客户端 -1
tooltip.columns 指定用于显示提示内容所需的列数 客户端 -1
tooltip.usePredefinedColor 指示此提示是否应使用物品颜色 客户端 false
tooltip.color usePredefinedColor 设置为 false 或无法自动确定物品颜色时,指定默认的提示颜色 客户端 NONE
tooltip.syncInterval 指定同步发生的频率 客户端 5000
tooltip.size 指定此提示的大小(如适用) 客户端 128
validation.enable 指示是否允许与此验证规则关联的操作 服务器 true
validation.suppressValidationInCreative 指示是否应抑制对创造模式玩家的此验证规则 服务器 true
validation.requiresPlayerOnGround 指示玩家是否应站在地面上以满足此验证规则 服务器 true
validation.requiresSilkTouch 指示玩家是否应手持带有精准采集附魔的工具以满足此验证规则 服务器 true

你可以直接在配置文件中编辑这些值中的任何一个,或者通过 ModMenu 进行编辑。


开发

依赖安装

要使用 API,请在你的构建脚本中将 Packed Inventory 添加为依赖:

build.gradle

repositories {
  maven {
    name = "Modrinth"
    url = "https://api.modrinth.com/maven"
    content {
      includeGroup "maven.modrinth"
    }
  }
}

dependencies {
  modImplementation "maven.modrinth:packed-inventory:${project.packed_inventory_version}"
}

gradle.properties

packed_inventory_version=/* 版本(例如,0.3.0+1.20.3) */

入口点

该 API 引入了两个新的入口点,你可以在模组元数据中指定:

名称 描述 接口
packed-inventory 将在任何环境中首先运行 PackedInventoryInitializer *
packed-inventory-client 将仅在客户端侧第二个运行 PackedInventoryClientInitializer client

fabric.mod.json

{
  "entrypoints": {
    "packed-inventory": [
      "a.b.c.MyMod"
    ],
    "packed-inventory-client": [
      "a.b.c.MyClientMod"
    ]
  }
}

背包视图

来认识一下背包视图!这是本模组的核心概念,使我们无需与对应方块的方块形式交互,即可编辑各自物品提供的背包。

让我们为末影箱物品注册一个简单的背包查看器 (提供背包视图的组件)

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerInventoryViewers(InventoryViewerRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            // 我们需要从给定的上下文中“提取”一个背包。
            // 在这种情况下,这很简单,因为末影箱有一个
            // 可以从玩家那里获取的共享背包。
            (inventory, slot, player) -> player.getEnderChestInventory(),

            // 虽然这绝对可以是一个谓词 -
            // `(inventory, slot, player) -> inventory.getStack(slot).isOf(Items.ENDER_CHEST)`,
            // 但这种形式更受青睐。
            Items.ENDER_CHEST
        );
    }
}

就这样!你应该能够打开游戏,将鼠标悬停在背包中的末影箱上,按下 k,这将会弹出背包编辑界面。

接下来,让我们建立一些规则,以确保我们的背包查看器公平且不赖皮地运行。要编辑末影箱背包,玩家应该:

  • 站在地面上,并且
  • 手持带有精准采集附魔的镐,或者
  • 处于创造模式。

为了实现这一点,我们将使用 InventoryValidators 类,其中包含一些有用的预定义背包验证器。请记住,你随时可以从头开始创建新的验证器。

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerInventoryViewers(InventoryViewerRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            (inventory, slot, player) -> player.getEnderChestInventory(),

            // isInCreative || isOnGround && hasSilkTouch
            InventoryValidators.IS_IN_CREATIVE.or(
                InventoryValidators.IS_ON_GROUND.and(InventoryValidators.HAS_PICKAXE_WITH_SILK_TOUCH)
            ),

            Items.ENDER_CHEST
        );
    }
}

我们快成功了!现在,当你尝试在半空中和/或不手持带有精准采集附魔的镐时打开末影箱,将会看到一条友好的错误消息。但是,一旦你切换到创造模式,所有限制都将消失。

最后要做的一件事是让我们的背包查看器可配置。毕竟,我们有 config 参数,这是有原因的 ;)

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerInventoryViewers(InventoryViewerRegistry registry, PackedInventoryApiConfig config) {
        // 让我们为末影箱物品注册一个验证配置条目。
        // API 提供了这些预定义配置(尽管你可以创建新的):
        // - ValidationConfig:可以启用/禁用。
        // - GenericValidationConfig:扩展 ValidationConfig,定义了在地面上的规则以及抑制对创造模式玩家的验证。
        // - SilkTouchableGenericValidationConfig:扩展 GenericValidationConfig,定义了背包中拥有精准采集工具的规定。
        config.registerValidationConfig(Items.ENDER_CHEST, SilkTouchableGenericValidationConfig::new);

        // 用户可以在配置中完全禁用背包查看器。
        var isEnabled = InventoryValidators.config(Items.ENDER_CHEST, config, ValidationConfig.DEFAULT, ValidationConfig::isEnabled);

        // 仅当配置允许时才抑制对创造模式玩家的验证。
        var isInCreative = InventoryValidators.config(Items.ENDER_CHEST, config, GenericValidationConfig.DEFAULT, GenericValidationConfig::isSuppressedInCreative).and(InventoryValidators.IS_IN_CREATIVE);

        // 仅当配置允许时才要求玩家在地面上。
        var isOnGround = InventoryValidators.config(Items.ENDER_CHEST, config, GenericValidationConfig.DEFAULT, x -> !x.requiresPlayerOnGround()).or(InventoryValidators.IS_ON_GROUND);

        // 仅当配置允许时才要求玩家背包中有精准采集工具。
        var hasSilkTouch = InventoryValidators.config(Items.ENDER_CHEST, config, SilkTouchableGenericValidationConfig.DEFAULT, x -> !x.requiresSilkTouch()).or(InventoryValidators.HAS_PICKAXE_WITH_SILK_TOUCH);

        registry.register(
            (inventory, slot, player) -> player.getEnderChestInventory(),

            isEnabled.and(isInCreative.or(isOnGround.and(hasSilkTouch))),

            Items.ENDER_CHEST
        );
    }
}

这样,所有限制都可以通过 ModMenu 或直接在配置文件中进行配置。

之后,你就成功地将一个物品与 Packed Inventory 集成完毕。恭喜!

背包视图处理器

我们已经了解了背包视图,但那个背包编辑界面是从哪里来的?那就是 InventoryViewHandler 的用武之地。

大多数情况下,如果你的背包满足以下条件,则无需注册自定义处理器:

  • 实现了 NamedScreenHandlerFactory,或者
  • 实现了 ScreenHandlerFactory,或者
  • 具有可由 GenericContainerScreenHandler 处理的常规大小 (即 9、18、27、36、45 或 54)

但是,和往常一样,你可以注册自己的 InventoryViewHandler

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerInventoryViewHandlers(InventoryViewHandlerRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            (inventoryView, parentInventory, slot, player) -> {
                var name = parentInventory.getStack(slot).getName();
                var playerInventory = player.getInventory();
                var namedFactory = new SimpleNamedScreenHandlerFactory((sId, inv, p) -> new GenericContainerScreenHandler(ScreenHandlerType.GENERIC_9X3, sId, playerInventory, inventoryView, 3), name);

                player.openHandledScreen(namedFactory);
            },

            Items.ENDER_CHEST
        );
    }
}

请注意,你不仅限于打开一个处理界面;你可以对提供的背包视图做任何你想做的事情。

背包验证失败处理器

我们可以自定义背包视图的处理,那么失败处理呢?当然,你也可以调整。

默认情况下,错误会发送到相应玩家的操作栏,但你可以更改此行为:

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerInventoryValidationFailureHandlers(InventoryValidationFailureHandlerRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            (failureReason, inventory, slot, player) -> {
                var text = failureReason.toText();
                if (text != null) {
                    player.sendMessage(text, false);
                }
            },

            Items.ENDER_CHEST
        );
    }
}

悬浮提示

没有描述性悬浮提示的背包管理算什么?让我们使用 TooltipProvider 为末影箱创建一个!

// 请注意,悬浮提示完全是客户端的,所以这里我们使用
// `PackedInventoryClientInitializer` 而不是 `PackedInventoryInitializer`。
public class MyClientMod implements PackedInventoryClientInitializer {
    @Override
    public void registerTooltipProviders(TooltipProviderRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            TooltipProvider.builder()
                .tooltipText((stack, context) -> List.of(Text.of("这是一个末影箱")))
                .build()

            Items.ENDER_CHEST
        );
    }
}

这样,末影箱的提示将被替换为文本“这是一个末影箱”。然而,这并没有帮助,不是吗?末影箱的问题在于客户端没有任何关于其内容的信息。那么,这是死胡同吗?当然不是。我们将使用一些魔法

public class MyClientMod implements PackedInventoryClientInitializer {
    @Override
    public void registerTooltipProviders(TooltipProviderRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            TooltipProvider.builder()
                .useSyncData(GenericContainerTooltipSyncData::of)
                .tooltipData((stack, context, syncData) -> GenericContainerTooltipData.of(syncData.getInventory()))
                .build()

            Items.ENDER_CHEST
        );
    }
}

在指定我们的 TooltipProvider 需要 TooltipSyncData (在这种情况下,我使用预定义的 GenericContainerTooltipSyncData,因为我们只关心末影箱的背包) 之后,我们可以在后续的 tooltipDatatooltipText 等调用中使用它。我们将稍后深入探讨 TooltipSyncData;现在,让我们假设一切都能正常工作。

请注意,我们用 GenericContainerTooltipData 覆盖了末影箱的 TooltipData。如果你不熟悉原版对此类事物的处理方式,当物品提供 TooltipData 时,Minecraft 会寻找相应的 TooltipComponent。此组件允许你在提示中渲染任何你想要的内容。GenericContainerTooltipData 由 API 提供,因此你无需担心为其向游戏提供提示组件。但是,如果你想使用自定义提示数据,你需要像这样通过 Fabric API 手动注册一个提示组件:

TooltipComponentCallback.EVENT.register(tooltipData -> {
    if (tooltipData instanceof MyTooltipData) {
        return new MyTooltipComponent((MyTooltipData)tooltipData);
    }
    return null;
});

好了,现在我们有了一个花哨的提示,可以显示末影箱的内容。再次,是时候让一切可配置了:

public class MyClientMod implements PackedInventoryClientInitializer {
    @Override
    public void registerTooltipProviders(TooltipProviderRegistry registry, PackedInventoryApiConfig config) {
        // 让我们为末影箱物品注册一个提示配置条目。
        // API 提供了这些预定义配置(尽管你可以创建新的):
        // - TooltipConfig:可以启用/禁用;紧凑模式可以开启或关闭。
        // - GenericTooltipConfig:扩展 TooltipConfig,定义了容器常用的属性。
        // - GenericSyncedTooltipConfig:扩展 GenericTooltipConfig,定义了需要同步数据的提示提供者使用的 `syncInterval` 属性。
        config.registerTooltipConfig(Items.ENDER_CHEST, GenericSyncedTooltipConfig::new);

        registry.register(
            TooltipProvider.builder()
                // 提示可以在配置中或通过按键(默认为 `Left Shift`)启用/禁用
                .when((stack, context) -> config.getTooltipConfigOrDefault(stack.getItem()).isEnabled())
                .useSyncData(GenericContainerTooltipSyncData::of)
                .tooltipData((stack, context, syncData) -> {
                    var cfg = config.getTooltipConfigOrDefault(stack.getItem());
                    var inventory = syncData.getInventory();
                    if (!cfg.shouldShowWhenEmpty() && inventory.isEmpty()) {
                        return null;
                    }

                    var color = cfg.usePredefinedColor() ? DyeColor.GRAY : cfg.color();
                    if (cfg.isCompact()) {
                        return GenericContainerTooltipData.ofZipped(inventory, cfg.rows(), cfg.columns(), color);
                    } else {
                        return GenericContainerTooltipData.of(inventory, cfg.rows(), cfg.columns(), color);
                    }
                })
                // 使此 TooltipProvider 的同步间隔可配置
                .syncInterval(stack -> config.getTooltipConfigOrDefault(stack.getItem(), GenericSyncedTooltipConfig.DEFAULT).syncInterval())
                .build(),

            Items.ENDER_CHEST
        );
    }
}

提示同步数据提供者

如前所述,为了使上面的示例工作,我们需要来自服务器的一些信息。这就是 TooltipSyncDataTooltipSyncDataProviders 的用武之地。

我们在末影箱的提示提供者中已经使用了 GenericContainerTooltipSyncData,所以让我们回到服务器端,教它如何处理它:

public class MyMod implements PackedInventoryInitializer {
    @Override
    public void registerTooltipSyncDataProviders(TooltipSyncDataProviderRegistry registry, PackedInventoryApiConfig config) {
        registry.register(
            // 在服务器端,我们需要创建相同的 TooltipSyncData
            // 并用客户端不存在的内容填充它。
            // 其他一切将由 Packed Inventory 自动处理。
            (stack, player) -> GenericContainerTooltipSyncData.of(player.getEnderChestInventory()),

            Items.ENDER_CHEST
        );
    }
}

只需几行代码,一切都能按预期工作。


安装

要求:

  • Minecraft 1.20.x
  • Fabric Loader >=0.15.0
  • Fabric API >=0.83.0

你可以从以下位置下载模组:

从源码构建

要求:

  • JDK 17

Linux/MacOS

git clone https://github.com/Kir-Antipov/packed-inventory.git
cd packed-inventory

chmod +x ./gradlew
./gradlew build
cd build/libs

Windows

git clone https://github.com/Kir-Antipov/packed-inventory.git
cd packed-inventory

gradlew build
cd build/libs