Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestAssertException;
Expand All @@ -24,6 +25,8 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.BundleContents;
import net.minecraft.world.item.component.ItemContainerContents;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
Expand All @@ -49,6 +52,7 @@
import org.cyclops.colossalchests2.blockentity.BlockEntityChestCore;
import org.cyclops.colossalchests2.blockentity.BlockEntityChestWall;
import org.cyclops.colossalchests2.blockentity.DisplayOption;
import org.cyclops.colossalchests2.capability.ItemHandlerLogic;
import org.cyclops.colossalchests2.capability.WallAccess;
import org.cyclops.colossalchests2.config.ChestTables;
import org.cyclops.colossalchests2.config.ChestTablesLoader;
Expand All @@ -74,6 +78,7 @@
import org.cyclops.colossalchests2.storage.CompressionFamily;
import org.cyclops.colossalchests2.storage.DeepSlot;
import org.cyclops.colossalchests2.storage.DisplayStats;
import org.cyclops.colossalchests2.storage.NestedChests;
import org.cyclops.colossalchests2.upgrade.ChestUpgrade;
import org.cyclops.colossalchests2.upgrade.ChestUpgradeInventory;
import org.cyclops.colossalchests2.upgrade.ChestUpgradeRules;
Expand Down Expand Up @@ -1966,4 +1971,45 @@ public void testDisplayWallOnlyMinedWithAPickaxe(GameTestHelper helper) {
.thenSucceed();
}

// Nested chests

@GameTest(template = TEMPLATE_EMPTY)
public void testFilledCoresCanNotBeStored(GameTestHelper helper) {
BlockPos corePosA = buildChest(helper, MIN_A, 3, ChestMaterial.WOOD);
BlockPos corePosB = buildChest(helper, MIN_B, 3, ChestMaterial.WOOD);
helper.startSequence()
.thenWaitUntil(() -> {
assertFormed(helper, corePosA, MIN_A, 3);
assertFormed(helper, corePosB, MIN_B, 3);
})
.thenExecute(() -> {
helper.assertValueEqual(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(RegistryEntries.COMPONENT_CHEST_CONTENTS.value()),
NestedChests.CONTENTS_COMPONENT, "contents component id");
getCore(helper, corePosA).getStorage().insert(STONE, 100, false);
ItemStack filledCore = breakCoreAndPickUp(helper, corePosA);
helper.assertTrue(filledCore.has(RegistryEntries.COMPONENT_CHEST_CONTENTS.value()), "Expected a core with contents");
ChestStorage storage = getCore(helper, corePosB).getStorage();
// Not through any insert path, and no slot can be locked to it.
helper.assertValueEqual(storage.insert(filledCore, 1, false), 0L, "filled core inserted");
helper.assertValueEqual(storage.insertAutomated(filledCore, 1, false), 0L, "filled core inserted by automation");
helper.assertTrue(ItemStack.matches(new ItemHandlerLogic(storage).insertItem(0, filledCore.copy(), false), filledCore),
"Expected an item handler to refuse a filled core");
helper.assertFalse(storage.lockTo(0, filledCore), "Expected no lock to a filled core");
// Nor inside a shulker box or bundle.
ItemStack shulker = new ItemStack(Items.SHULKER_BOX);
shulker.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(List.of(filledCore)));
helper.assertValueEqual(storage.insert(shulker, 1, false), 0L, "shulker box with a filled core inserted");
ItemStack bundle = new ItemStack(Items.BUNDLE);
bundle.set(DataComponents.BUNDLE_CONTENTS, new BundleContents(List.of(shulker)));
helper.assertValueEqual(storage.insert(bundle, 1, false), 0L, "bundle with a filled core inserted");
// Empty cores and other containers are fine.
ItemStack emptyCore = new ItemStack(filledCore.getItem());
helper.assertValueEqual(storage.insert(emptyCore, 1, false), 1L, "empty core inserted");
ItemStack stoneShulker = new ItemStack(Items.SHULKER_BOX);
stoneShulker.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(List.of(STONE.copyWithCount(64))));
helper.assertValueEqual(storage.insert(stoneShulker, 1, false), 1L, "shulker box with stone inserted");
})
.thenSucceed();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public boolean isExtractOnly(int slot) {
* @return If the type may go into the slot, ignoring how full it is.
*/
public boolean canAccept(int slot, ItemStack type) {
if (type.isEmpty() || getCapacity(type) <= 0) {
if (type.isEmpty() || getCapacity(type) <= 0 || !NestedChests.canStore(type)) {
return false;
}
DeepSlot deepSlot = slots[slot];
Expand Down Expand Up @@ -334,7 +334,7 @@ public boolean setLocked(int slot, boolean locked) {
*/
public boolean lockTo(int slot, ItemStack type) {
DeepSlot deepSlot = slots[slot];
if (type.isEmpty()) {
if (type.isEmpty() || !NestedChests.canStore(type)) {
return false;
}
type = getStoredType(type);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package org.cyclops.colossalchests2.storage;

import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.BundleContents;
import net.minecraft.world.item.component.ItemContainerContents;
import org.cyclops.colossalchests2.Reference;

import java.util.Map;
import java.util.Optional;

/**
* Keeps chest cores that carry items out of chests, so cores can not be nested in each other without limit and
* overflow the size of saved data. Also looks inside vanilla containers, such as shulker boxes and bundles.
* @author rubensworks
*/
public final class NestedChests {

/**
* The item component of a core item that carries the chest's contents.
*/
public static final ResourceLocation CONTENTS_COMPONENT = ResourceLocation.fromNamespaceAndPath(Reference.MOD_ID, "chest_contents");
// Deeper nesting is refused outright, so a crafted stack can not make this check itself expensive.
private static final int MAX_DEPTH = 8;

private NestedChests() {
}

/**
* @param stack An item stack.
* @return If a chest may store the stack: it is not a core with contents, and holds none.
*/
public static boolean canStore(ItemStack stack) {
return !holdsChestContents(stack, 0);
}

private static boolean holdsChestContents(ItemStack stack, int depth) {
DataComponentPatch patch = stack.getComponentsPatch();
if (patch.isEmpty()) {
return false;
}
if (depth >= MAX_DEPTH) {
return true;
}
// Compared by id, so this works without the mod's registries, such as in unit tests.
for (Map.Entry<DataComponentType<?>, Optional<?>> entry : patch.entrySet()) {
if (entry.getValue().isPresent() && CONTENTS_COMPONENT.equals(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(entry.getKey()))) {
return true;
}
}
ItemContainerContents container = stack.get(DataComponents.CONTAINER);
if (container != null) {
for (ItemStack inner : container.nonEmptyItems()) {
if (holdsChestContents(inner, depth + 1)) {
return true;
}
}
}
BundleContents bundle = stack.get(DataComponents.BUNDLE_CONTENTS);
if (bundle != null) {
for (ItemStack inner : bundle.items()) {
if (holdsChestContents(inner, depth + 1)) {
return true;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.cyclops.colossalchests2.storage;

import net.minecraft.core.component.DataComponents;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.BundleContents;
import net.minecraft.world.item.component.ItemContainerContents;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* Cores with contents are refused through game tests, which have the mod's item components.
* @author rubensworks
*/
public class TestNestedChests extends BootstrapTest {

private static ItemStack shulkerWith(ItemStack... contents) {
ItemStack shulker = new ItemStack(Items.SHULKER_BOX);
shulker.set(DataComponents.CONTAINER, ItemContainerContents.fromItems(List.of(contents)));
return shulker;
}

@Test
public void testPlainItems() {
assertTrue(NestedChests.canStore(new ItemStack(Items.STONE)));
assertTrue(NestedChests.canStore(new ItemStack(Items.SHULKER_BOX)));
}

@Test
public void testContainers() {
assertTrue(NestedChests.canStore(shulkerWith(new ItemStack(Items.STONE, 64))));
ItemStack bundle = new ItemStack(Items.BUNDLE);
bundle.set(DataComponents.BUNDLE_CONTENTS, new BundleContents(List.of(shulkerWith(new ItemStack(Items.STONE)))));
assertTrue(NestedChests.canStore(bundle));
}

@Test
public void testTooDeep() {
ItemStack stack = new ItemStack(Items.STONE);
for (int i = 0; i < 9; i++) {
stack = shulkerWith(stack);
}
assertFalse(NestedChests.canStore(stack));
}
}
Loading