Skip to content
Draft
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 @@ -10,6 +10,7 @@
import dev.ryanhcode.sable.companion.math.BoundingBox3ic;
import dev.ryanhcode.sable.companion.math.JOMLConversion;
import dev.ryanhcode.sable.companion.math.Pose3d;
import dev.ryanhcode.sable.index.SableTags;
import dev.ryanhcode.sable.platform.SableAssemblyPlatform;
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
import dev.ryanhcode.sable.sublevel.SubLevel;
Expand Down Expand Up @@ -348,11 +349,19 @@ public static void moveBlocks(final ServerLevel level, final AssemblyTransform t
tag.putInt("z", newPos.getZ());
}

if (blockEntity instanceof final RandomizableContainer container) {
container.setLootTable(null);
}
if (blockEntity instanceof final Clearable clearable) {
clearable.clearContent();
if (state.is(SableTags.SILENT_ASSEMBLY_REMOVAL)) {
level.removeBlockEntity(block);
} else {
// This is the "correct" way to remove a block from the world, but many mods do not implement
// Clearable correctly. The above tag exists to allow this issue to be "fixed" on a case-by-case
// basis without updating a mod's code
//
// A real solution is to implement Clearable on all block entities that can be cleared in the
// same way as Vanilla MC. See SetBlockCommand
if (blockEntity instanceof final RandomizableContainer container) {
container.setLootTable(null);
}
Clearable.tryClear(blockEntity);
}

final LevelChunk chunk = resultingAccelerator.getChunk(SectionPos.blockToSectionCoord(newPos.getX()), SectionPos.blockToSectionCoord(newPos.getZ()));
Expand Down
5 changes: 5 additions & 0 deletions common/src/main/java/dev/ryanhcode/sable/index/SableTags.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class SableTags {
Sable.sablePath("paddles")
);

public static final TagKey<Block> SILENT_ASSEMBLY_REMOVAL = TagKey.create(
Registries.BLOCK,
Sable.sablePath("silent_assembly_removal")
);

public static void register() {
// no-op
}
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"replace": false,
"values": [
]
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fabric_version=0.110.0+1.21.1
fabric_loader_version=0.16.9

# NeoForge, see https://projects.neoforged.net/neoforged/neoforge for new versions
neoforge_version=21.1.220
neoforge_version=21.1.228
neoforge_loader_version_range=[4,)

# Dependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
package dev.ryanhcode.sable.neoforge.gametest;

import static dev.ryanhcode.sable.neoforge.gametest.SableTestHelper.removeSubLevel;
import dev.ryanhcode.sable.Sable;
import dev.ryanhcode.sable.api.SubLevelAssemblyHelper;
import dev.ryanhcode.sable.companion.math.BoundingBox3i;
import dev.ryanhcode.sable.companion.math.BoundingBox3ic;
import dev.ryanhcode.sable.api.sublevel.ServerSubLevelContainer;
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
import dev.ryanhcode.sable.companion.math.BoundingBox3i;
import dev.ryanhcode.sable.companion.math.BoundingBox3ic;
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
import dev.ryanhcode.sable.sublevel.plot.EmbeddedPlotLevelAccessor;
import dev.ryanhcode.sable.sublevel.system.SubLevelPhysicsSystem;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestAssertPosException;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.neoforged.neoforge.capabilities.Capabilities;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.items.IItemHandler;
import net.neoforged.neoforge.items.IItemHandlerModifiable;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaterniond;
import org.joml.Vector3d;
import org.joml.Vector3i;
import org.joml.Vector3ic;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

@GameTestHolder(Sable.MOD_ID)
public final class AssemblyTest {
Expand All @@ -36,9 +56,6 @@ public static void testBrittleBreaking(final GameTestHelper helper) {
}

final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
if (physicsSystem == null) {
throw new IllegalStateException("Plot container does not have physics");
}

final BlockPos min = helper.absolutePos(new BlockPos(0, 1, 0));
final BlockPos max = helper.absolutePos(new BlockPos(2, 3, 2));
Expand Down Expand Up @@ -89,4 +106,173 @@ public static void testBrittleBreaking(final GameTestHelper helper) {
}
});
}

@GameTest(template = "allblocks", manualOnly = true, timeoutTicks = 30_000_000)
public static void testAllBlocks(final GameTestHelper helper) {
final boolean failOnFirstError = false;
final Set<ResourceLocation> skip = Set.of(
// ResourceLocation.fromNamespaceAndPath("create", "millstone"),
// ResourceLocation.fromNamespaceAndPath("create", "brass_tunnel")
);
final Direction[] capabilityDirections = {
null,
Direction.DOWN,
Direction.UP,
Direction.NORTH,
Direction.SOUTH,
Direction.WEST,
Direction.EAST
};

final ServerLevel level = helper.getLevel();
final ServerSubLevelContainer plotContainer = SubLevelContainer.getContainer(level);
if (plotContainer == null) {
throw new IllegalStateException("Plot container not found in level");
}

final SubLevelPhysicsSystem physicsSystem = plotContainer.physicsSystem();
final ItemStack insertStack = new ItemStack(Items.OCELOT_SPAWN_EGG);

final BlockPos pos = helper.absolutePos(new BlockPos(2, 3, 2));
final BlockPos onPos = pos.below();
final List<BlockState> invalidStates = new LinkedList<>();
final List<BlockState> failures = new LinkedList<>();

// long tick = 0;
for (final Map.Entry<ResourceKey<Block>, Block> entry : BuiltInRegistries.BLOCK.entrySet()) {
if (skip.contains(entry.getKey().location())) {
continue;
}

final Block block = entry.getValue();
for (final BlockState state : block.getStateDefinition().getPossibleStates()) {
// helper.runAtTickTime(tick += 3, () -> {
level.setBlock(onPos, Blocks.STONE.defaultBlockState(), 2);
level.setBlock(pos, state, 2);

// The block was unstable and can't be placed in this configuration
if (level.getBlockState(pos) != state) {
helper.killAllEntities();
invalidStates.add(state);
return;
}

// Bug with lecterns. If the block state is set, but there isn't a book it will try to drop an air item
if (state.is(Blocks.LECTERN) && state.getValue(BlockStateProperties.HAS_BOOK)) {
return;
}

final List<Entity> startEntities = level.getEntities(EntityTypeTest.forClass(Entity.class), helper.getBounds(), Entity::isAlive);

int insertCount = 0;
for (@Nullable final Direction direction : capabilityDirections) {
final IItemHandler inventory = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, state, level.getBlockEntity(pos), direction);
if (inventory != null) {
final int slots = inventory.getSlots();
if (inventory instanceof final IItemHandlerModifiable modifiable) {
for (int i = 0; i < slots; i++) {
try {
modifiable.setStackInSlot(i, insertStack.copy());
insertCount++;
} catch (final Throwable ignored) {
if (!inventory.insertItem(i, insertStack.copy(), false).equals(insertStack)) {
insertCount++;
}
}
}
} else {
for (int i = 0; i < slots; i++) {
if (!inventory.insertItem(i, insertStack.copy(), false).equals(insertStack)) {
insertCount++;
}
}
}
}
}

if (insertCount == 0 && !state.hasBlockEntity()) {
return;
}

// helper.runAfterDelay(1, () -> {
final ServerSubLevel subLevel = SubLevelAssemblyHelper.assembleBlocks(level, pos, List.of(pos, onPos), new BoundingBox3i(
onPos.getX(),
onPos.getY(),
onPos.getZ(),
pos.getX() + 1,
pos.getY() + 1,
pos.getZ() + 1));
physicsSystem.getPipeline().teleport(subLevel,
new Vector3d(pos.getX() + 0.5,
pos.getY() + 1.0,
pos.getZ() + 0.5),
helper.getTestRotation().rotation().transformation().getNormalizedRotation(new Quaterniond()));

final EmbeddedPlotLevelAccessor subLevelAccessor = subLevel.getPlot().getEmbeddedLevelAccessor();
final BlockEntity sublevelBlockEntity = subLevelAccessor.getBlockEntity(BlockPos.ZERO);

final List<Entity> resultEntities = level.getEntities(EntityTypeTest.forClass(Entity.class), helper.getBounds(), Entity::isAlive);
if (startEntities.size() != resultEntities.size()) {
if (failOnFirstError) {
final List<Component> names = new ArrayList<>(resultEntities.size());
for (final Entity entity : resultEntities) {
if (!startEntities.contains(entity)) {
names.add(entity.getDisplayName());
}
}
final String formattedEntities = ComponentUtils.formatList(names, Component.literal(", ")).getString();
helper.fail(state + " failed. Expected " + startEntities.size() + " entities to exist, found " + formattedEntities);
}
failures.add(state);
}

// TODO check if items are the same

// helper.runAfterDelay(1, () -> {
removeSubLevel(plotContainer, subLevel);
// });
// });
// });
}
}

// helper.runAtTickTime(tick + 1, () -> {
if (!invalidStates.isEmpty()) {
final List<Component> names = new ArrayList<>(invalidStates.size());
for (final BlockState state : invalidStates) {
names.add(formatBlockState(state));
}
final String formattedLines = ComponentUtils.formatList(names, Component.literal("\n")).getString();
Sable.LOGGER.info("Skipped states:\n{}", formattedLines);
}

if (!failures.isEmpty()) {
final List<Component> names = new ArrayList<>(failures.size());
for (final BlockState state : failures) {
names.add(formatBlockState(state));
}
final String formattedLines = ComponentUtils.formatList(names, Component.literal("\n")).getString();
helper.fail(failures.size() + " states failed.\n" + formattedLines);
}

helper.succeed();
// });
}

private static Component formatBlockState(final BlockState state) {
final MutableComponent name = Component.literal(String.valueOf(BuiltInRegistries.BLOCK.getKey(state.getBlock())));

final Collection<Property<?>> properties = state.getProperties();
if (!properties.isEmpty()) {
final StringBuilder propertiesString = new StringBuilder("[");
for (final Property<?> property : properties) {
final Object value = state.getValue(property);
propertiesString.append(property.getName()).append("=").append(value).append(",");
}
propertiesString.setCharAt(propertiesString.length() - 1, ']');
name.append(propertiesString.toString());
}

return name;
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package dev.ryanhcode.sable.neoforge.gametest;

import dev.ryanhcode.sable.companion.math.Pose3d;
import dev.ryanhcode.sable.api.sublevel.SubLevelContainer;
import dev.ryanhcode.sable.companion.math.Pose3d;
import dev.ryanhcode.sable.sublevel.ServerSubLevel;
import dev.ryanhcode.sable.sublevel.SubLevel;
import dev.ryanhcode.sable.sublevel.plot.LevelPlot;
import dev.ryanhcode.sable.sublevel.storage.SubLevelRemovalReason;
import net.minecraft.core.BlockPos;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.CommonLevelAccessor;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import org.joml.Vector2i;
import org.joml.Vector3d;
import org.joml.Vector3dc;

import java.util.function.Consumer;

public final class SableTestHelper {
Expand Down Expand Up @@ -78,4 +79,10 @@ public static boolean isInBounds(final GameTestHelper helper, final Vector3dc gl
final AABB box = helper.getBounds();
return box.contains(globalPosition.x(), globalPosition.y(), globalPosition.z());
}

public static void removeSubLevel(final SubLevelContainer container, final ServerSubLevel subLevel) {
final LevelPlot plot = subLevel.getPlot();
final Vector2i origin = container.getOrigin();
container.removeSubLevel(plot.plotPos.x - origin.x, plot.plotPos.z - origin.y, SubLevelRemovalReason.REMOVED);
}
}
Loading