add support for non-supported bags/packs. If there is no compat layer yet for a bag/pack item from a mod, then attempt to place that bag into your hotbar and simulate a right click to open it. Prefer empty slots, non-tool slots, non-food slots if it needs to swap for something currently on your hotbar.

This commit is contained in:
trunksbomb
2026-03-23 03:43:29 -04:00
parent 274e4f6dec
commit 6271417381
6 changed files with 526 additions and 2 deletions

View File

@@ -131,6 +131,9 @@ dependencies {
localRuntime "curse.maven:sophisticated-core-618298:7635107" localRuntime "curse.maven:sophisticated-core-618298:7635107"
localRuntime "curse.maven:sophisticated-backpacks-422301:7645643" localRuntime "curse.maven:sophisticated-backpacks-422301:7645643"
localRuntime "curse.maven:dank-storage-335673:7539725" localRuntime "curse.maven:dank-storage-335673:7539725"
localRuntime "curse.maven:puzzles-lib-495476:6850909"
localRuntime "curse.maven:forge-config-api-port-547434:7204750"
localRuntime "curse.maven:bag-of-holding-593996:7020146"
} }
// This block of code expands all declared replace properties in the specified resource targets. // This block of code expands all declared replace properties in the specified resource targets.

View File

@@ -0,0 +1,409 @@
package net.minecraft.server.level;
import com.mojang.logging.LogUtils;
import java.util.Objects;
import javax.annotation.Nullable;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import org.slf4j.Logger;
public class ServerPlayerGameMode {
private static final Logger LOGGER = LogUtils.getLogger();
protected ServerLevel level;
protected final ServerPlayer player;
private GameType gameModeForPlayer = GameType.DEFAULT_MODE;
@Nullable
private GameType previousGameModeForPlayer;
private boolean isDestroyingBlock;
private int destroyProgressStart;
private BlockPos destroyPos = BlockPos.ZERO;
private int gameTicks;
private boolean hasDelayedDestroy;
private BlockPos delayedDestroyPos = BlockPos.ZERO;
private int delayedTickStart;
private int lastSentState = -1;
public ServerPlayerGameMode(ServerPlayer player) {
this.player = player;
this.level = player.serverLevel();
}
public boolean changeGameModeForPlayer(GameType gameModeForPlayer) {
if (gameModeForPlayer == this.gameModeForPlayer) {
return false;
} else {
this.setGameModeForPlayer(gameModeForPlayer, this.previousGameModeForPlayer);
this.player.onUpdateAbilities();
this.player
.server
.getPlayerList()
.broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
this.level.updateSleepingPlayerList();
if (gameModeForPlayer == GameType.CREATIVE) {
this.player.resetCurrentImpulseContext();
}
return true;
}
}
protected void setGameModeForPlayer(GameType gameModeForPlayer, @Nullable GameType previousGameModeForPlayer) {
this.previousGameModeForPlayer = previousGameModeForPlayer;
this.gameModeForPlayer = gameModeForPlayer;
// Neo: preserve flying state, removed on tick if Attribute or ability no longer applies
boolean wasFlying = this.player.getAbilities().flying;
gameModeForPlayer.updatePlayerAbilities(this.player.getAbilities());
this.player.getAbilities().flying = wasFlying || this.player.getAbilities().flying;
}
public GameType getGameModeForPlayer() {
return this.gameModeForPlayer;
}
@Nullable
public GameType getPreviousGameModeForPlayer() {
return this.previousGameModeForPlayer;
}
public boolean isSurvival() {
return this.gameModeForPlayer.isSurvival();
}
public boolean isCreative() {
return this.gameModeForPlayer.isCreative();
}
public void tick() {
this.gameTicks++;
if (this.hasDelayedDestroy) {
BlockState blockstate = this.level.getBlockState(this.delayedDestroyPos);
if (blockstate.isAir()) {
this.hasDelayedDestroy = false;
} else {
float f = this.incrementDestroyProgress(blockstate, this.delayedDestroyPos, this.delayedTickStart);
if (f >= 1.0F) {
this.hasDelayedDestroy = false;
this.destroyBlock(this.delayedDestroyPos);
}
}
} else if (this.isDestroyingBlock) {
BlockState blockstate1 = this.level.getBlockState(this.destroyPos);
if (blockstate1.isAir()) {
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
this.lastSentState = -1;
this.isDestroyingBlock = false;
} else {
this.incrementDestroyProgress(blockstate1, this.destroyPos, this.destroyProgressStart);
}
}
}
private float incrementDestroyProgress(BlockState state, BlockPos pos, int startTick) {
int i = this.gameTicks - startTick;
float f = state.getDestroyProgress(this.player, this.player.level(), pos) * (float)(i + 1);
int j = (int)(f * 10.0F);
if (j != this.lastSentState) {
this.level.destroyBlockProgress(this.player.getId(), pos, j);
this.lastSentState = j;
}
return f;
}
private void debugLogging(BlockPos pos, boolean terminate, int sequence, String message) {
}
public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction face, int maxBuildHeight, int sequence) {
net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.LeftClickBlock event = net.neoforged.neoforge.common.CommonHooks.onLeftClickBlock(player, pos, face, action);
if (event.isCanceled()) {
return;
}
if (!this.player.canInteractWithBlock(pos, 1.0)) {
this.debugLogging(pos, false, sequence, "too far");
} else if (pos.getY() >= maxBuildHeight) {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, "too high");
} else {
if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
if (!this.level.mayInteract(this.player, pos)) {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, "may not interact");
return;
}
if (this.isCreative()) {
this.destroyAndAck(pos, sequence, "creative destroy");
return;
}
if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, "block action restricted");
return;
}
this.destroyProgressStart = this.gameTicks;
float f = 1.0F;
BlockState blockstate = this.level.getBlockState(pos);
if (!blockstate.isAir()) {
EnchantmentHelper.onHitBlock(
this.level,
this.player.getMainHandItem(),
this.player,
this.player,
EquipmentSlot.MAINHAND,
Vec3.atCenterOf(pos),
blockstate,
p_348149_ -> this.player.onEquippedItemBroken(p_348149_, EquipmentSlot.MAINHAND)
);
if (event.getUseBlock() != net.neoforged.neoforge.common.util.TriState.FALSE)
blockstate.attack(this.level, pos, this.player);
f = blockstate.getDestroyProgress(this.player, this.player.level(), pos);
}
if (!blockstate.isAir() && f >= 1.0F) {
this.destroyAndAck(pos, sequence, "insta mine");
} else {
if (this.isDestroyingBlock) {
this.player.connection.send(new ClientboundBlockUpdatePacket(this.destroyPos, this.level.getBlockState(this.destroyPos)));
this.debugLogging(pos, false, sequence, "abort destroying since another started (client insta mine, server disagreed)");
}
this.isDestroyingBlock = true;
this.destroyPos = pos.immutable();
int i = (int)(f * 10.0F);
this.level.destroyBlockProgress(this.player.getId(), pos, i);
this.debugLogging(pos, true, sequence, "actual start of destroying");
this.lastSentState = i;
}
} else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
if (pos.equals(this.destroyPos)) {
int j = this.gameTicks - this.destroyProgressStart;
BlockState blockstate1 = this.level.getBlockState(pos);
if (!blockstate1.isAir()) {
float f1 = blockstate1.getDestroyProgress(this.player, this.player.level(), pos) * (float)(j + 1);
if (f1 >= 0.7F) {
this.isDestroyingBlock = false;
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
this.destroyAndAck(pos, sequence, "destroyed");
return;
}
if (!this.hasDelayedDestroy) {
this.isDestroyingBlock = false;
this.hasDelayedDestroy = true;
this.delayedDestroyPos = pos;
this.delayedTickStart = this.destroyProgressStart;
}
}
}
this.debugLogging(pos, true, sequence, "stopped destroying");
} else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
this.isDestroyingBlock = false;
if (!Objects.equals(this.destroyPos, pos)) {
LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos);
this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
}
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
this.debugLogging(pos, true, sequence, "aborted destroying");
}
}
}
public void destroyAndAck(BlockPos pos, int sequence, String message) {
if (this.destroyBlock(pos)) {
this.debugLogging(pos, true, sequence, message);
} else {
this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
this.debugLogging(pos, false, sequence, message);
}
}
/**
* Attempts to harvest a block
*/
public boolean destroyBlock(BlockPos pos) {
BlockState blockstate1 = this.level.getBlockState(pos);
var event = net.neoforged.neoforge.common.CommonHooks.fireBlockBreak(level, gameModeForPlayer, player, pos, blockstate1);
if (event.isCanceled()) {
return false;
} else {
BlockEntity blockentity = this.level.getBlockEntity(pos);
Block block = blockstate1.getBlock();
if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
this.level.sendBlockUpdated(pos, blockstate1, blockstate1, 3);
return false;
} else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
return false;
} else {
BlockState blockstate = block.playerWillDestroy(this.level, pos, blockstate1, this.player);
if (this.isCreative()) {
removeBlock(pos, blockstate, false);
return true;
} else {
ItemStack itemstack = this.player.getMainHandItem();
ItemStack itemstack1 = itemstack.copy();
boolean flag1 = blockstate.canHarvestBlock(this.level, pos, this.player); // previously player.hasCorrectToolForDrops(blockstate)
itemstack.mineBlock(this.level, blockstate, pos, this.player);
boolean flag = removeBlock(pos, blockstate, flag1);
if (flag1 && flag) {
block.playerDestroy(this.level, this.player, pos, blockstate, blockentity, itemstack1);
}
// Neo: Fire the PlayerDestroyItemEvent if the tool was broken at any point during the break process
if (itemstack.isEmpty() && !itemstack1.isEmpty()) {
net.neoforged.neoforge.event.EventHooks.onPlayerDestroyItem(this.player, itemstack1, InteractionHand.MAIN_HAND);
}
return true;
}
}
}
}
/**
* Patched-in method that handles actual removal of blocks for {@link #destroyBlock(BlockPos)}.
*
* @param pos The block pos of the destroyed block
* @param state The state of the destroyed block
* @param canHarvest If the player breaking the block can harvest the drops of the block
* @return If the block was removed, as reported by {@link BlockState#onDestroyedByPlayer}.
*/
private boolean removeBlock(BlockPos pos, BlockState state, boolean canHarvest) {
boolean removed = state.onDestroyedByPlayer(this.level, pos, this.player, canHarvest, this.level.getFluidState(pos));
if (removed)
state.getBlock().destroy(this.level, pos, state);
return removed;
}
public InteractionResult useItem(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand) {
if (this.gameModeForPlayer == GameType.SPECTATOR) {
return InteractionResult.PASS;
} else if (player.getCooldowns().isOnCooldown(stack.getItem())) {
return InteractionResult.PASS;
} else {
InteractionResult cancelResult = net.neoforged.neoforge.common.CommonHooks.onItemRightClick(player, hand);
if (cancelResult != null) return cancelResult;
int i = stack.getCount();
int j = stack.getDamageValue();
InteractionResultHolder<ItemStack> interactionresultholder = stack.use(level, player, hand);
ItemStack itemstack = interactionresultholder.getObject();
if (itemstack == stack && itemstack.getCount() == i && itemstack.getUseDuration(player) <= 0 && itemstack.getDamageValue() == j) {
return interactionresultholder.getResult();
} else if (interactionresultholder.getResult() == InteractionResult.FAIL && itemstack.getUseDuration(player) > 0 && !player.isUsingItem()) {
return interactionresultholder.getResult();
} else {
if (stack != itemstack) {
player.setItemInHand(hand, itemstack);
}
if (itemstack.isEmpty()) {
player.setItemInHand(hand, ItemStack.EMPTY);
}
if (!player.isUsingItem()) {
player.inventoryMenu.sendAllDataToRemote();
}
return interactionresultholder.getResult();
}
}
}
public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
BlockPos blockpos = hitResult.getBlockPos();
BlockState blockstate = level.getBlockState(blockpos);
if (!blockstate.getBlock().isEnabled(level.enabledFeatures())) {
return InteractionResult.FAIL;
}
net.neoforged.neoforge.event.entity.player.PlayerInteractEvent.RightClickBlock event = net.neoforged.neoforge.common.CommonHooks.onRightClickBlock(player, hand, blockpos, hitResult);
if (event.isCanceled()) return event.getCancellationResult();
if (this.gameModeForPlayer == GameType.SPECTATOR) {
MenuProvider menuprovider = blockstate.getMenuProvider(level, blockpos);
if (menuprovider != null) {
player.openMenu(menuprovider);
return InteractionResult.SUCCESS;
} else {
return InteractionResult.PASS;
}
} else {
UseOnContext useoncontext = new UseOnContext(player, hand, hitResult);
if (event.getUseItem() != net.neoforged.neoforge.common.util.TriState.FALSE) {
InteractionResult result = stack.onItemUseFirst(useoncontext);
if (result != InteractionResult.PASS) return result;
}
boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
boolean flag1 = (player.isSecondaryUseActive() && flag) && !(player.getMainHandItem().doesSneakBypassUse(level, blockpos, player) && player.getOffhandItem().doesSneakBypassUse(level, blockpos, player));
ItemStack itemstack = stack.copy();
if (event.getUseBlock().isTrue() || (event.getUseBlock().isDefault() && !flag1)) {
ItemInteractionResult iteminteractionresult = blockstate.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
if (iteminteractionresult.consumesAction()) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockpos, itemstack);
return iteminteractionresult.result();
}
if (iteminteractionresult == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION && hand == InteractionHand.MAIN_HAND) {
InteractionResult interactionresult = blockstate.useWithoutItem(level, player, hitResult);
if (interactionresult.consumesAction()) {
CriteriaTriggers.DEFAULT_BLOCK_USE.trigger(player, blockpos);
return interactionresult;
}
}
}
if (event.getUseItem().isTrue() || (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack.getItem()))) {
if (event.getUseItem().isFalse()) return InteractionResult.PASS;
InteractionResult interactionresult1;
if (this.isCreative()) {
int i = stack.getCount();
interactionresult1 = stack.useOn(useoncontext);
stack.setCount(i);
} else {
interactionresult1 = stack.useOn(useoncontext);
}
if (interactionresult1.consumesAction()) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockpos, itemstack);
}
return interactionresult1;
} else {
return InteractionResult.PASS;
}
}
}
/**
* Sets the world instance.
*/
public void setLevel(ServerLevel serverLevel) {
this.level = serverLevel;
}
}

View File

@@ -21,7 +21,7 @@ public final class BagAccess {
BagIdentityData.ensureBagId(stack); BagIdentityData.ensureBagId(stack);
} }
BagCompat.BagHandler handler = BagCompat.findHandler(stack); BagCompat.BagHandler handler = BagCompat.findHandler(stack);
if (handler != null) { if (handler != null || BagCompat.canShowInTabs(stack)) {
bags.add(new BagEntry(slot, stack.copy(), handler, BagCompat.getIdentity(stack))); bags.add(new BagEntry(slot, stack.copy(), handler, BagCompat.getIdentity(stack)));
} }
} }

View File

@@ -50,6 +50,10 @@ public final class BagCompat {
return !stack.isEmpty() && (findHandler(stack) != null || looksLikeInventoryItem(stack)); return !stack.isEmpty() && (findHandler(stack) != null || looksLikeInventoryItem(stack));
} }
public static boolean canShowInTabs(ItemStack stack) {
return canName(stack);
}
public static BagIdentity getIdentity(ItemStack stack) { public static BagIdentity getIdentity(ItemStack stack) {
if (!canName(stack)) { if (!canName(stack)) {
return null; return null;

View File

@@ -2,13 +2,27 @@ package com.trunksbomb.bagtabs.network;
import com.trunksbomb.bagtabs.BagTabs; import com.trunksbomb.bagtabs.BagTabs;
import com.trunksbomb.bagtabs.bag.BagCompat; import com.trunksbomb.bagtabs.bag.BagCompat;
import com.trunksbomb.bagtabs.bag.InventoryBag; import net.minecraft.core.component.DataComponents;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.BowItem;
import net.minecraft.world.item.CrossbowItem;
import net.minecraft.world.item.DiggerItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.ShearsItem;
import net.minecraft.world.item.ShieldItem;
import net.minecraft.world.item.SwordItem;
import net.minecraft.world.item.TridentItem;
import net.neoforged.neoforge.network.handling.IPayloadContext; import net.neoforged.neoforge.network.handling.IPayloadContext;
public record OpenBagPayload(int slot) implements CustomPacketPayload { public record OpenBagPayload(int slot) implements CustomPacketPayload {
@@ -37,6 +51,99 @@ public record OpenBagPayload(int slot) implements CustomPacketPayload {
BagCompat.BagHandler handler = BagCompat.findHandler(stack); BagCompat.BagHandler handler = BagCompat.findHandler(stack);
if (handler != null) { if (handler != null) {
handler.open(serverPlayer, payload.slot(), stack); handler.open(serverPlayer, payload.slot(), stack);
return;
}
if (BagCompat.canShowInTabs(stack)) {
if (!openViaHotbarFallback(serverPlayer, payload.slot())) {
serverPlayer.sendSystemMessage(BagTabs.translation("generic_open_failed"));
BagTabs.LOGGER.debug("Failed to open bag fallback for item {} in slot {}", stack, payload.slot());
}
} }
} }
private static boolean openViaHotbarFallback(ServerPlayer player, int sourceSlot) {
Inventory inventory = player.getInventory();
ItemStack sourceStack = inventory.getItem(sourceSlot);
if (sourceStack.isEmpty()) {
return false;
}
int targetHotbarSlot = chooseHotbarSlot(inventory, sourceSlot);
if (targetHotbarSlot < 0) {
return false;
}
if (sourceSlot != targetHotbarSlot) {
ItemStack hotbarStack = inventory.getItem(targetHotbarSlot).copy();
inventory.setItem(targetHotbarSlot, sourceStack.copy());
inventory.setItem(sourceSlot, hotbarStack);
}
inventory.selected = targetHotbarSlot;
player.connection.send(new ClientboundSetCarriedItemPacket(targetHotbarSlot));
player.inventoryMenu.broadcastChanges();
ItemStack mainHandStack = player.getItemInHand(InteractionHand.MAIN_HAND);
if (mainHandStack.isEmpty()) {
return false;
}
int previousContainerId = player.containerMenu.containerId;
InteractionResult result = player.gameMode.useItem(player, player.level(), mainHandStack, InteractionHand.MAIN_HAND);
player.inventoryMenu.broadcastChanges();
return result.consumesAction() || player.containerMenu.containerId != previousContainerId;
}
private static int chooseHotbarSlot(Inventory inventory, int sourceSlot) {
if (sourceSlot >= 0 && sourceSlot < Inventory.getSelectionSize()) {
return sourceSlot;
}
for (int hotbarSlot = 0; hotbarSlot < Inventory.getSelectionSize(); hotbarSlot++) {
if (inventory.getItem(hotbarSlot).isEmpty()) {
return hotbarSlot;
}
}
for (int hotbarSlot = 0; hotbarSlot < Inventory.getSelectionSize(); hotbarSlot++) {
if (isBuildingBlock(inventory.getItem(hotbarSlot))) {
return hotbarSlot;
}
}
for (int hotbarSlot = 0; hotbarSlot < Inventory.getSelectionSize(); hotbarSlot++) {
if (isPreferredMiscItem(inventory.getItem(hotbarSlot))) {
return hotbarSlot;
}
}
return -1;
}
private static boolean isBuildingBlock(ItemStack stack) {
return !stack.isEmpty() && stack.getItem() instanceof BlockItem && !isTorch(stack.getItem());
}
private static boolean isPreferredMiscItem(ItemStack stack) {
if (stack.isEmpty()) {
return false;
}
Item item = stack.getItem();
return !stack.has(DataComponents.FOOD)
&& !isTorch(item)
&& !(item instanceof DiggerItem)
&& !(item instanceof SwordItem)
&& !(item instanceof BowItem)
&& !(item instanceof CrossbowItem)
&& !(item instanceof TridentItem)
&& !(item instanceof ShieldItem)
&& !(item instanceof ShearsItem)
&& !(item instanceof ProjectileWeaponItem);
}
private static boolean isTorch(Item item) {
return item instanceof BlockItem blockItem
&& blockItem.getBlock().getDescriptionId().toLowerCase(java.util.Locale.ROOT).contains("torch");
}
} }

View File

@@ -36,6 +36,7 @@
"bagtabs.dock.side.screen_right": "Dock: Screen Right", "bagtabs.dock.side.screen_right": "Dock: Screen Right",
"bagtabs.dock.side.floating_horizontal": "Dock: Floating Horizontal", "bagtabs.dock.side.floating_horizontal": "Dock: Floating Horizontal",
"bagtabs.dock.side.floating_vertical": "Dock: Floating Vertical", "bagtabs.dock.side.floating_vertical": "Dock: Floating Vertical",
"bagtabs.generic_open_failed": "No safe hotbar slot was available to open that bag.",
"key.categories.bagtabs": "Bag Tabs", "key.categories.bagtabs": "Bag Tabs",
"key.bagtabs.open_last_bag": "Open Last Bag" "key.bagtabs.open_last_bag": "Open Last Bag"
} }