From d1f79d66554c5a6286296213f7fdbfc79ffd8feb Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 23 May 2025 05:22:26 -0400 Subject: [PATCH] Fix warped and crimson fungi not being able to be placed on nylium. (#10544) Fix other PlantType parity issues, add test for all vanilla PlantType blocks. Co-authored-by: LexManos --- .../net/minecraft/core/BlockPos.java.patch | 11 + .../world/level/block/Block.java.patch | 14 +- .../StructureTemplateManager.java.patch | 10 + .../net/minecraftforge/common/ForgeHooks.java | 52 +- .../net/minecraftforge/common/IPlantable.java | 10 +- .../net/minecraftforge/common/PlantType.java | 2 + .../common/extensions/IForgeBlockPos.java | 31 ++ .../data/forge/structure/empty3x3x3.nbt | Bin 202 -> 0 bytes .../plant_type_placement/bamboo.json | 7 + .../plant_type_placement/cactus.json | 7 + .../plant_type_placement/cactus_flower.json | 7 + .../plant_type_placement/coverage.json | 7 + .../plant_type_placement/dry_vegitation.json | 7 + .../plant_type_placement/farmland.json | 7 + .../plant_type_placement/fungus.json | 7 + .../plant_type_placement/mushroom.json | 7 + .../plant_type_placement/nether_fungus.json | 7 + .../nether_vegitation.json | 7 + .../plant_type_placement/netherwart.json | 7 + .../plant_type_placement/sugarcane.json | 7 + .../plant_type_placement/vegitation.json | 7 + .../vegitation_and_clay.json | 7 + .../plant_type_placement/withrose.json | 7 + .../block/PlantTypePlacementTest.java | 523 ++++++++++++++++++ .../net/minecraftforge/test/BaseTestMod.java | 4 +- 25 files changed, 749 insertions(+), 13 deletions(-) create mode 100644 patches/minecraft/net/minecraft/core/BlockPos.java.patch create mode 100644 patches/minecraft/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java.patch create mode 100644 src/main/java/net/minecraftforge/common/extensions/IForgeBlockPos.java delete mode 100644 src/main/resources/data/forge/structure/empty3x3x3.nbt create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/bamboo.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus_flower.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/coverage.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/dry_vegitation.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/farmland.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/fungus.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/mushroom.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_fungus.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_vegitation.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/netherwart.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/sugarcane.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation_and_clay.json create mode 100644 src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/withrose.json create mode 100644 src/test/java/net/minecraftforge/debug/gameplay/block/PlantTypePlacementTest.java diff --git a/patches/minecraft/net/minecraft/core/BlockPos.java.patch b/patches/minecraft/net/minecraft/core/BlockPos.java.patch new file mode 100644 index 0000000000..1ef54a492f --- /dev/null +++ b/patches/minecraft/net/minecraft/core/BlockPos.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/core/BlockPos.java ++++ b/net/minecraft/core/BlockPos.java +@@ -31,7 +_,7 @@ + import org.slf4j.Logger; + + @Immutable +-public class BlockPos extends Vec3i { ++public class BlockPos extends Vec3i implements net.minecraftforge.common.extensions.IForgeBlockPos { + public static final Codec CODEC = Codec.INT_STREAM + .comapFlatMap( + p_325638_ -> Util.fixedSize(p_325638_, 3).map(p_175270_ -> new BlockPos(p_175270_[0], p_175270_[1], p_175270_[2])), diff --git a/patches/minecraft/net/minecraft/world/level/block/Block.java.patch b/patches/minecraft/net/minecraft/world/level/block/Block.java.patch index 8aaf02dbf2..6828dd5d76 100644 --- a/patches/minecraft/net/minecraft/world/level/block/Block.java.patch +++ b/patches/minecraft/net/minecraft/world/level/block/Block.java.patch @@ -108,7 +108,7 @@ } public boolean hasDynamicShape() { -@@ -556,5 +_,74 @@ +@@ -556,5 +_,78 @@ public int hashCode() { return System.identityHashCode(this.first) * 31 + System.identityHashCode(this.second); } @@ -148,18 +148,22 @@ + return state.is(Blocks.CACTUS) || state.is(BlockTags.SAND); + } else if (plant.getBlock() == Blocks.SUGAR_CANE && this == Blocks.SUGAR_CANE) { + return true; -+ } else if (plantable instanceof BushBlock bush && bush.mayPlaceOn(state, world, pos)) { ++ } else if (plantable instanceof VegetationBlock veg && veg.mayPlaceOn(state, world, pos)) { + return true; + } else if (net.minecraftforge.common.PlantType.DESERT.equals(type)) { -+ return state.is(BlockTags.SAND) || this == Blocks.TERRACOTTA || this instanceof GlazedTerracottaBlock; ++ return state.is(BlockTags.SAND) || state.is(BlockTags.TERRACOTTA) || state.is(BlockTags.DIRT); + } else if (net.minecraftforge.common.PlantType.NETHER.equals(type)) { + return this == Blocks.SOUL_SAND; ++ } else if (net.minecraftforge.common.PlantType.FUNGUS.equals(type)) { ++ return state.is(BlockTags.NYLIUM) || this == Blocks.MYCELIUM || this == Blocks.SOUL_SOIL; + } else if (net.minecraftforge.common.PlantType.CROP.equals(type)) { + return state.is(Blocks.FARMLAND); + } else if (net.minecraftforge.common.PlantType.CAVE.equals(type)) { -+ return state.isFaceSturdy(world, pos, Direction.UP); ++ return state.isFaceSturdy(world, pos, Direction.UP) && state.canOcclude(); + } else if (net.minecraftforge.common.PlantType.PLAINS.equals(type)) { -+ return state.is(BlockTags.DIRT) || this == Blocks.FARMLAND; ++ return state.is(BlockTags.DIRT) || this == Blocks.FARMLAND; ++ } else if (net.minecraftforge.common.PlantType.MOIST.equals(type)) { ++ return state.is(BlockTags.DIRT) || this == Blocks.CLAY || this == Blocks.FARMLAND; + } else if (net.minecraftforge.common.PlantType.WATER.equals(type)) { + return (state.is(Blocks.WATER) || state.getBlock() instanceof IceBlock) && world.getFluidState(pos.relative(facing)).isEmpty(); + } else if (net.minecraftforge.common.PlantType.BEACH.equals(type)) { diff --git a/patches/minecraft/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java.patch b/patches/minecraft/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java.patch new file mode 100644 index 0000000000..3fa1eef5a1 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java ++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java +@@ -76,6 +_,7 @@ + } + + builder.add(new StructureTemplateManager.Source(this::loadFromResource, this::listResources)); ++ builder.add(new StructureTemplateManager.Source(net.minecraftforge.common.ForgeHooks::createEmptyStructure, Stream::empty)); + this.sources = builder.build(); + } + diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index b783de66d8..2e7509ed2b 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -45,8 +46,11 @@ import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.arguments.selector.EntitySelectorParser; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.Holder.Reference; +import net.minecraft.core.HolderGetter; import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; +import net.minecraft.core.HolderSet.Named; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; @@ -83,6 +87,7 @@ import net.minecraft.world.inventory.ContainerLevelAccess; import net.minecraft.world.item.*; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.material.Fluids; import net.minecraft.world.level.storage.LevelStorageSource; @@ -100,6 +105,7 @@ import net.minecraft.world.inventory.AnvilMenu; import net.minecraft.world.Container; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.IntTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; @@ -190,6 +196,8 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.pattern.BlockInWorld; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; + import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.jetbrains.annotations.ApiStatus; @@ -209,7 +217,7 @@ public final class ForgeHooks { public static void fireLightingCalculatedEvent(ChunkAccess chunk) { MinecraftForge.EVENT_BUS.post(new ChunkEvent.LightingCalculated(chunk)); } - + public static boolean canContinueUsing(@NotNull ItemStack from, @NotNull ItemStack to) { if (!from.isEmpty() && !to.isEmpty()) { return from.getItem().canContinueUsing(from, to); @@ -1263,4 +1271,46 @@ public final class ForgeHooks { return value; } + + private static final Pattern EMPTY_SIZE_PATTERN = Pattern.compile("^empty(\\d+)x(\\d+)x(\\d+)$"); + + /* + * Creates a empty structure of any size requested using the pattern `forge:empty(x)x(y)x(z)` + * + * This is useful for game tests that need a structure, but want to create the contents in code. + */ + public static Optional createEmptyStructure(ResourceLocation name) { + if (name== null || !"forge".equals(name.getNamespace())) + return Optional.empty(); + + var match = EMPTY_SIZE_PATTERN.matcher(name.getPath()); + if (!match.matches()) + return Optional.empty(); + + var size = new BlockPos(Integer.parseInt(match.group(1)), Integer.parseInt(match.group(2)), Integer.parseInt(match.group(3))); + + var blocks = new ListTag(); + for (var pos : BlockPos.betweenClosed(BlockPos.ZERO, size)) { + blocks.add(CompoundTag.builder() + .tag("pos", pos.toListTag()) + .build() + ); + } + + var ret = new StructureTemplate(); + ret.load(new HolderGetter() { + @SuppressWarnings("deprecation") + @Override + public Optional> get(ResourceKey key) { + return Optional.of(Blocks.AIR.builtInRegistryHolder()); + } + @Override public Optional> get(TagKey key) { return Optional.empty(); } + }, CompoundTag.builder() + .tag("size", size.toListTag()) + .tag("blocks", blocks) + .build() + ); + + return Optional.of(ret); + } } \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/IPlantable.java b/src/main/java/net/minecraftforge/common/IPlantable.java index 7f106a3d8e..c7b0440aa6 100644 --- a/src/main/java/net/minecraftforge/common/IPlantable.java +++ b/src/main/java/net/minecraftforge/common/IPlantable.java @@ -5,12 +5,8 @@ package net.minecraftforge.common; -import net.minecraft.world.level.block.CropBlock; -import net.minecraft.world.level.block.FlowerBlock; -import net.minecraft.world.level.block.SaplingBlock; +import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.AttachedStemBlock; -import net.minecraft.world.level.block.Blocks; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; @@ -18,6 +14,8 @@ public interface IPlantable { default PlantType getPlantType(BlockGetter level, BlockPos pos) { if (this instanceof AttachedStemBlock) return PlantType.CROP; if (this instanceof CropBlock) return PlantType.CROP; + if (this == Blocks.MANGROVE_PROPAGULE) return PlantType.MOIST; + if (this instanceof AzaleaBlock) return PlantType.MOIST; if (this instanceof SaplingBlock) return PlantType.PLAINS; if (this instanceof FlowerBlock) return PlantType.PLAINS; if (this == Blocks.PITCHER_CROP) return PlantType.CROP; @@ -25,6 +23,8 @@ public interface IPlantable { if (this == Blocks.LILY_PAD) return PlantType.WATER; if (this == Blocks.RED_MUSHROOM) return PlantType.CAVE; if (this == Blocks.BROWN_MUSHROOM) return PlantType.CAVE; + if (this == Blocks.CRIMSON_FUNGUS) return PlantType.FUNGUS; + if (this == Blocks.WARPED_FUNGUS) return PlantType.FUNGUS; if (this == Blocks.NETHER_WART) return PlantType.NETHER; if (this == Blocks.TALL_GRASS) return PlantType.PLAINS; return PlantType.PLAINS; diff --git a/src/main/java/net/minecraftforge/common/PlantType.java b/src/main/java/net/minecraftforge/common/PlantType.java index 318b8cf757..3d9d71370c 100644 --- a/src/main/java/net/minecraftforge/common/PlantType.java +++ b/src/main/java/net/minecraftforge/common/PlantType.java @@ -27,6 +27,8 @@ public final class PlantType public static final PlantType WATER = get("water"); public static final PlantType NETHER = get("nether"); public static final PlantType CROP = get("crop"); + public static final PlantType FUNGUS = get("fungus"); + public static final PlantType MOIST = get("moist"); /** * Getting a custom {@link PlantType}, or an existing one if it has the same name as that one. Your plant should implement {@link IPlantable} diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeBlockPos.java b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockPos.java new file mode 100644 index 0000000000..b7ae7f65af --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeBlockPos.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.extensions; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; + +public interface IForgeBlockPos { + private BlockPos self() { return (BlockPos) this; } + + default ListTag toListTag() { + var tag = new ListTag(); + tag.add(IntTag.valueOf(self().getX())); + tag.add(IntTag.valueOf(self().getY())); + tag.add(IntTag.valueOf(self().getZ())); + return tag; + } + + default CompoundTag toCompoundTag() { + return CompoundTag.builder() + .putInt("x", self().getX()) + .putInt("y", self().getY()) + .putInt("z", self().getZ()) + .build(); + } +} diff --git a/src/main/resources/data/forge/structure/empty3x3x3.nbt b/src/main/resources/data/forge/structure/empty3x3x3.nbt deleted file mode 100644 index ac4f797f1c1d9c9fcf8a9b9154247d15b9d8ec53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202 zcmb2|=3oGW|Gk&bavd@dV0mye_v*g~!aqdfY^%S$u$?B_^mp;elgqcWYIEE==fu|- zxzAt%(}{^}|F^IlxPEeCq;%~UF$3Sew`vbhBb!YSZKDIrK_nn;oIsV6Wm1P%Wb2;{9H-736pBYj)+m3~S0RVt6TulG~ diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/bamboo.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/bamboo.json new file mode 100644 index 0000000000..25bf35d738 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/bamboo.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/bamboo", + "max_ticks": 100, + "structure": "forge:empty5x4x4" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus.json new file mode 100644 index 0000000000..ff9a1d0e00 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/cactus", + "max_ticks": 100, + "structure": "forge:empty4x4x5" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus_flower.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus_flower.json new file mode 100644 index 0000000000..304f33d688 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/cactus_flower.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/cactus_flower", + "max_ticks": 100, + "structure": "forge:empty3x4x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/coverage.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/coverage.json new file mode 100644 index 0000000000..341c7acd6a --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/coverage.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/coverage", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/dry_vegitation.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/dry_vegitation.json new file mode 100644 index 0000000000..77da6e5424 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/dry_vegitation.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/dry_vegitation", + "max_ticks": 100, + "structure": "forge:empty31x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/farmland.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/farmland.json new file mode 100644 index 0000000000..83b7ab7d8d --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/farmland.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/farmland", + "max_ticks": 100, + "structure": "forge:empty3x3x4" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/fungus.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/fungus.json new file mode 100644 index 0000000000..86fd3ea05d --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/fungus.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/fungus", + "max_ticks": 100, + "structure": "forge:empty11x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/mushroom.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/mushroom.json new file mode 100644 index 0000000000..44c6dfedf9 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/mushroom.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/mushroom", + "max_ticks": 100, + "structure": "forge:empty16x4x16" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_fungus.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_fungus.json new file mode 100644 index 0000000000..6fb405074e --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_fungus.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/nether_fungus", + "max_ticks": 100, + "structure": "forge:empty15x3x2" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_vegitation.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_vegitation.json new file mode 100644 index 0000000000..ceaf682111 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/nether_vegitation.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/nether_vegitation", + "max_ticks": 100, + "structure": "forge:empty14x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/netherwart.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/netherwart.json new file mode 100644 index 0000000000..ffe17ef076 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/netherwart.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/netherwart", + "max_ticks": 100, + "structure": "forge:empty3x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/sugarcane.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/sugarcane.json new file mode 100644 index 0000000000..1c01b534e4 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/sugarcane.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/sugarcane", + "max_ticks": 100, + "structure": "forge:empty13x3x9" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation.json new file mode 100644 index 0000000000..064822d49f --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/vegitation", + "max_ticks": 100, + "structure": "forge:empty11x3x30" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation_and_clay.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation_and_clay.json new file mode 100644 index 0000000000..971f5a3bed --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/vegitation_and_clay.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/vegitation_and_clay", + "max_ticks": 100, + "structure": "forge:empty12x3x3" +} \ No newline at end of file diff --git a/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/withrose.json b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/withrose.json new file mode 100644 index 0000000000..b45a510c99 --- /dev/null +++ b/src/test/generated/plant_type_placement/data/forge/test_instance/plant_type_placement/withrose.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:function", + "environment": "minecraft:default", + "function": "forge:plant_type_placement/withrose", + "max_ticks": 100, + "structure": "forge:empty4x3x4" +} \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/gameplay/block/PlantTypePlacementTest.java b/src/test/java/net/minecraftforge/debug/gameplay/block/PlantTypePlacementTest.java new file mode 100644 index 0000000000..575f68954a --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/gameplay/block/PlantTypePlacementTest.java @@ -0,0 +1,523 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.gameplay.block; + +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.gametest.framework.*; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.IPlantable; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.gametest.GameTest; +import net.minecraftforge.gametest.GameTestNamespace; +import net.minecraftforge.test.BaseTestMod; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.Streams; +import static net.minecraft.world.level.block.Blocks.*; + +@GameTestNamespace("forge") +@Mod(PlantTypePlacementTest.MOD_ID) +public class PlantTypePlacementTest extends BaseTestMod { + static final String MOD_ID = "plant_type_placement"; + + private static final Map, List> TAGS = Util.make(new HashMap<>(), map -> { + map.put(BlockTags.DIRT, List.of(DIRT, GRASS_BLOCK, PODZOL, COARSE_DIRT, MYCELIUM, ROOTED_DIRT, MOSS_BLOCK, PALE_MOSS_BLOCK, MUD, MUDDY_MANGROVE_ROOTS)); + map.put(BlockTags.SAND, List.of(SAND, RED_SAND, SUSPICIOUS_SAND)); + map.put(BlockTags.TERRACOTTA, List.of(TERRACOTTA, WHITE_TERRACOTTA, ORANGE_TERRACOTTA, MAGENTA_TERRACOTTA, LIGHT_BLUE_TERRACOTTA, YELLOW_TERRACOTTA, LIME_TERRACOTTA, PINK_TERRACOTTA, GRAY_TERRACOTTA, LIGHT_GRAY_TERRACOTTA, CYAN_TERRACOTTA, PURPLE_TERRACOTTA, BLUE_TERRACOTTA, BROWN_TERRACOTTA, GREEN_TERRACOTTA, RED_TERRACOTTA, BLACK_TERRACOTTA)); + map.put(BlockTags.NYLIUM, List.of(CRIMSON_NYLIUM, WARPED_NYLIUM)); + map.put(BlockTags.MUSHROOM_GROW_BLOCK, List.of(MYCELIUM, PODZOL, CRIMSON_NYLIUM, WARPED_NYLIUM)); + }); + + private record Info(Consumer handler, List blocks) {} + private static Info of(Consumer handler, Block... blocks) { + return new Info(handler, Arrays.asList(blocks)); + } + + private final Map, Info> PLANTS = Util.make(new HashMap<>(), map -> { + map.put(BambooStalkBlock.class, of(this::bamboo, BAMBOO)); + map.put(CactusBlock.class, of(this::cactus, CACTUS)); + map.put(SugarCaneBlock.class, of(this::sugarcane, SUGAR_CANE)); + + // VegitationBlock has no direct creations + map.put(AttachedStemBlock.class, of(this::farmland, ATTACHED_MELON_STEM, ATTACHED_PUMPKIN_STEM)); + map.put(AzaleaBlock.class, of(this::vegitation_and_clay, AZALEA, FLOWERING_AZALEA)); + map.put(BushBlock.class, of(this::vegitation, BUSH)); + map.put(CactusFlowerBlock.class, of(this::cactus_flower, CACTUS_FLOWER)); + + map.put(CropBlock.class, of(this::farmland, WHEAT)); + map.put(BeetrootBlock.class, of(this::farmland, BEETROOTS)); + map.put(CarrotBlock.class, of(this::farmland, CARROTS)); + map.put(PotatoBlock.class, of(this::farmland, POTATOES)); + map.put(TorchflowerCropBlock.class, of(this::farmland, TORCHFLOWER_CROP)); + + map.put(DoublePlantBlock.class, of(this::todo, LARGE_FERN, PITCHER_PLANT, TALL_GRASS)); + map.put(PitcherCropBlock.class, of(this::farmland, PITCHER_CROP)); + map.put(SmallDripleafBlock.class, of(this::todo, SMALL_DRIPLEAF)); + map.put(TallFlowerBlock.class, of(this::todo, LILAC, PEONY, ROSE_BUSH, SUNFLOWER)); + map.put(TallSeagrassBlock.class, of(this::todo, TALL_SEAGRASS)); + + map.put(DryVegetationBlock.class, of(this::dry_vegitation, DEAD_BUSH)); + map.put(ShortDryGrassBlock.class, of(this::dry_vegitation, SHORT_DRY_GRASS)); + map.put(TallDryGrassBlock.class, of(this::dry_vegitation, TALL_DRY_GRASS)); + + map.put(FireflyBushBlock.class, of(this::vegitation, FIREFLY_BUSH)); + map.put(FlowerBedBlock.class, of(this::vegitation, PINK_PETALS, WILDFLOWERS)); + + map.put(FlowerBlock.class, of(this::vegitation, ALLIUM, AZURE_BLUET, BLUE_ORCHID, CORNFLOWER, DANDELION, LILY_OF_THE_VALLEY, ORANGE_TULIP, OXEYE_DAISY, PINK_TULIP, POPPY, RED_TULIP, TORCHFLOWER, WHITE_TULIP)); + map.put(EyeblossomBlock.class, of(this::vegitation, CLOSED_EYEBLOSSOM, OPEN_EYEBLOSSOM)); + map.put(WitherRoseBlock.class, of(this::withrose, WITHER_ROSE)); + + map.put(FungusBlock.class, of(this::nether_fungus, CRIMSON_FUNGUS, WARPED_FUNGUS)); + map.put(LeafLitterBlock.class, of(this::todo, LEAF_LITTER)); + map.put(MushroomBlock.class, of(this::mushroom, BROWN_MUSHROOM, RED_MUSHROOM)); + map.put(NetherSproutsBlock.class, of(this::nether_vegitation, NETHER_SPROUTS)); + map.put(NetherWartBlock.class, of(this::netherwart, NETHER_WART)); + map.put(RootsBlock.class, of(this::nether_vegitation, CRIMSON_ROOTS, WARPED_ROOTS)); + + map.put(SaplingBlock.class, of(this::vegitation, ACACIA_SAPLING, BIRCH_SAPLING, CHERRY_SAPLING, DARK_OAK_SAPLING, JUNGLE_SAPLING, OAK_SAPLING, PALE_OAK_SAPLING, SPRUCE_SAPLING)); + map.put(MangrovePropaguleBlock.class, of(this::vegitation_and_clay, MANGROVE_PROPAGULE)); + + map.put(SeagrassBlock.class, of(this::todo, SEAGRASS)); + map.put(SeaPickleBlock.class, of(this::todo, SEA_PICKLE)); + map.put(StemBlock.class, of(this::farmland, MELON_STEM, PUMPKIN_STEM)); + map.put(SweetBerryBushBlock.class, of(this::vegitation, SWEET_BERRY_BUSH)); + map.put(TallGrassBlock.class, of(this::vegitation, FERN, SHORT_GRASS)); + map.put(WaterlilyBlock.class, of(this::todo, LILY_PAD)); + }); + + @SafeVarargs + private List plants(Class... classes) { + var ret = new ArrayList(); + for (var cls : classes) + ret.addAll(PLANTS.get(cls).blocks()); + return ret; + } + + private void todo(GameTestHelper helper) { + // TODO: [Forge][Plant][Test] Implement the final tests for some vanilla blocks + // DoublePlantBlock = UPPER == self LOWER == VEGITATION + // SmallDripleafBlock = UPPER = self LOWER == SMALL_DRIPLEAF_PLACEABLE || water_source || VEGITATION + // TallFlowerBlock + // TallSeagrassBlock = UPPER = self LOWER == Solid Up Face && !MAGMA_BLOCK + + // LeafLitterBlock = Sturdy UP face + // SeagrassBlock = Sturdy UP face && !MAGMA_BLOCK + // SeaPickleBlock = Sturdy UP Face || Face UP != empty? + // WaterlilyBlock = (Water Fluid || IceBlock) && self.fluid == Empty + } + + public PlantTypePlacementTest(FMLJavaModLoadingContext context) { + super(context); + } + + // Tests to make sure that we know about all plants in vanilla + @GameTest + public void coverage(GameTestHelper helper) { + var vanilla = helper.registryLookup(Registries.BLOCK).entrySet() + .stream() + .filter(e -> e.getValue() instanceof IPlantable && e.getKey().location().getNamespace().equals("minecraft")) + .map(Map.Entry::getValue) + .collect(Collectors.groupingBy(Object::getClass)); + + var missing = new HashSet<>(PLANTS.keySet()); + for (var entry : vanilla.entrySet()) { + var known = PLANTS.get(entry.getKey()); + helper.assertTrue(known != null, "Unknown IPlantable concrete class: " + entry.getKey()); + helper.assertValueEqual(sort(known.blocks()), sort(entry.getValue()), entry.getKey().getSimpleName(), "Does not have expected values"); + missing.remove(entry.getKey()); + } + + helper.assertValueEqual(Collections.emptySet(), missing, "Missing", "Expected plants were missing"); + helper.succeed(); + } + + @GameTest(structure = "forge:empty5x4x4") // sand(3) + dirt(10) + extra(4) = 17 + public void bamboo(GameTestHelper helper) { + // BambooStalkBlock = Tag.BAMBOO_PLANTABLE_ON + var plants = plants(BambooStalkBlock.class); + var soils = new ArrayList<>(sandAndDirt(helper, BAMBOO, BAMBOO_SAPLING, GRAVEL, SUSPICIOUS_GRAVEL)); + // these need special case + var special = List.of(BAMBOO, BAMBOO_SAPLING); + + testTag(helper, BlockTags.BAMBOO_PLANTABLE_ON, soils); + soils.removeAll(special); + + var pos = iterate(helper, plants, soils); + + for (var plant : plants) { + for (var soil : special) { + pos = offset(helper, pos); + var sandPos = pos.above(); + var soilPos = sandPos.above(); + var plantPos = soilPos.above(); + + helper.setAndAssertBlock(pos, STONE); + helper.setAndAssertBlock(sandPos, SAND); + helper.setAndAssertBlock(soilPos, soil); + canSurvive(helper, plantPos, plant); + } + } + + helper.succeed(); + } + + @GameTest(structure = "forge:empty4x4x5") // sand(3) + cactus * 2 for spacing + we need a 1 block border because BARRIOR is solid + public void cactus(GameTestHelper helper) { + // CactusBlock = (Horizontal != solid && !Fluid.LAVA) && (Tag.SAND, CACTUS) && !above().liquid() + var plants = plants(CactusBlock.class); + var soils = sand(helper); // Add an extra sand so it builds somewhere to test cactus + + int idx = 0; + for (var plant : plants) { + BlockPos pos = null; + for (var soil : soils) { + pos = new BlockPos(idx % 2 == 0 ? 1 : 2, 0, idx + 1); + var soilPos = pos.above(); + var plantPos = soilPos.above(); + + helper.setAndAssertBlock(pos, STONE); + helper.setAndAssertBlock(soilPos, soil); + canSurvive(helper, plantPos, plant); + + idx++; + } + // Test on top of the last placed cactus + canSurvive(helper, pos.above(3), plant); + } + + // TODO: [Forge][Plants][Test] Test solid/lava next to cactus + + helper.succeed(); + } + + @GameTest(structure = "forge:empty3x4x3") // Need 4 high for CACTUS soil + public void cactus_flower(GameTestHelper helper) { + helper.makeFloor(STONE, 0); + helper.makeFloor(SAND, 1); + + var pos = BlockPos.ZERO.above(); + // CactusFlowerBlock = CACTUS, FARMLAND, SturdyCenter Up + for (var plant : plants(CactusFlowerBlock.class)) { + helper.setAndAssertBlock(pos, FARMLAND); + canSurvive(helper, pos.above(), plant); + + var cactus = pos.offset(1, 1, 1); + helper.setAndAssertBlock(cactus, CACTUS); + canSurvive(helper, cactus.above(), plant); + + //TODO: [Forge][Plants][Test] Center Support for Cactus Flower + + pos.east(3); + } + helper.succeed(); + } + + @GameTest(structure = "forge:empty13x3x9") // (sand(3) + dirt(10)) * (water(2) + FROSTED_ICE) + public void sugarcane(GameTestHelper helper) { + // SugarCaneBlock = DIRT || SAND && Horizontal (Fluid == WATER || FROSTED_ICE) + var plants = plants(SugarCaneBlock.class); + var soils = sandAndDirt(helper); + + var start = BlockPos.ZERO; + for (var plant : plants) { + for (var soil : soils) { + var pos = start; + for (int z = 0; z < 9; z++) { + helper.setBlock(pos, STONE); + helper.setBlock(pos.above(), soil); + pos = pos.south(); + } + + + // Place fluids + pos = start.above(); + helper.setAndAssertBlock(pos.south(2), WATER); + helper.setAndAssertBlock(pos.south(3), WATER.defaultBlockState().setValue(LiquidBlock.LEVEL, 8)); + helper.setAndAssertBlock(pos.south(6), FROSTED_ICE); + + // Test Plants + pos = start.above(); + canSurvive(helper, pos.above().south(1), plant); + canSurvive(helper, pos.above().south(4), plant); + canSurvive(helper, pos.above().south(7), plant); + + start = start.east(); + } + } + helper.succeed(); + } + + @GameTest(structure = "forge:empty11x3x30") // (dirt(10) + FARMLAND) * 30 plants + public void vegitation(GameTestHelper helper) { + var plants = plants( + BushBlock.class, // 1 + FireflyBushBlock.class, // 1 + FlowerBedBlock.class, // 2 + FlowerBlock.class, // 13 + EyeblossomBlock.class, // 2 + SaplingBlock.class, // 8 + SweetBerryBushBlock.class, // 1 + TallGrassBlock.class // 2 + ); + var soils = vegitationSoils(helper); + + iterate(helper, plants, soils); + helper.succeed(); + } + + @GameTest(structure = "forge:empty12x3x3") // (dirt(10) + FARMLAND + CLAY) * 2 plants + public void vegitation_and_clay(GameTestHelper helper) { + var plants = plants( + MangrovePropaguleBlock.class, // 1 + AzaleaBlock.class // 2 + ); + var soils = vegitationSoils(helper, CLAY); + + iterate(helper, plants, soils); + helper.succeed(); + } + + @GameTest(structure = "forge:empty4x3x4") // (dirt(10) + FARMLAND + 3 extra) * 1 plant = 14 + public void withrose(GameTestHelper helper) { + //WitherRoseBlock = VEGITATION || NETHERRACK, SOUL_SAND, SOUL_SOIL + var plants = plants(WitherRoseBlock.class); + iterate(helper, plants, vegitationSoils(helper, NETHERRACK, SOUL_SAND, SOUL_SOIL)); + helper.succeed(); + } + + @GameTest + public void netherwart(GameTestHelper helper) { + // NetherWartBlock = SOUL_SAND + var plants = plants(NetherWartBlock.class); + iterate(helper, plants, List.of(SOUL_SAND)); + helper.succeed(); + } + + @GameTest(structure = "forge:empty14x3x3") // (dirt(10) + FARMLAND + nylium(2) + SOUL_SOIL) * 3 plants + public void nether_vegitation(GameTestHelper helper) { + // NetherSproutsBlock = VEGITATION || Tags.NYLIUM, SOUL_SOIL + // RootsBlock = VEGITATION || Tags.NYLIUM, SOUL_SOIL + var plants = plants( + NetherSproutsBlock.class, // 1 + RootsBlock.class // 2 + ); + var nylium = testTag(helper, BlockTags.NYLIUM, TAGS.get(BlockTags.NYLIUM)); + var soils = Stream.concat(nylium.stream(), vegitationSoils(helper, SOUL_SOIL).stream()).distinct().toList(); + + iterate(helper, plants, soils); + helper.succeed(); + } + + @GameTest(structure = "forge:empty15x3x2") // (dirt(10) + FARMLAND + nylium(2) + SOUL_SOIL + MYCELIUM) * 2 plants + public void nether_fungus(GameTestHelper helper) { + // FungusBlock = VEGITATION || Tags.NYLIUM, MYCELIUM, SOUL_SOIL + var plants = plants(FungusBlock.class); + var nylium = testTag(helper, BlockTags.NYLIUM, TAGS.get(BlockTags.NYLIUM)); + var soils = Stream.concat(nylium.stream(), vegitationSoils(helper, MYCELIUM, SOUL_SOIL).stream()).distinct().toList(); + + iterate(helper, plants, soils); + helper.succeed(); + } + + @GameTest(structure = "forge:empty3x3x4") // 4 stems + 6 crops + public void farmland(GameTestHelper helper) { + var plants = plants( + AttachedStemBlock.class, // 2 + StemBlock.class, // 2 + CropBlock.class, + BeetrootBlock.class, + CarrotBlock.class, + PotatoBlock.class, + TorchflowerCropBlock.class, + PitcherCropBlock.class + ); + iterate(helper, plants, List.of(FARMLAND)); + helper.succeed(); + } + + @GameTest(structure = "forge:empty31x3x3") // 3 plants (sand(3) + dirt(10) + terracotta(17) + FARMLAND) * plants(3) + public void dry_vegitation(GameTestHelper helper) { + // DryVegetationBlock = Tag.DRY_VEGETATION_MAY_PLACE_ON + var plants = plants(DryVegetationBlock.class, ShortDryGrassBlock.class, TallDryGrassBlock.class); + var terracotta = testTag(helper, BlockTags.TERRACOTTA, TAGS.get(BlockTags.TERRACOTTA)); + var soils = Streams.concat(sandAndDirt(helper, FARMLAND).stream(), terracotta.stream()).toList(); + testTag(helper, BlockTags.DRY_VEGETATION_MAY_PLACE_ON, soils); + iterate(helper, plants, soils); + helper.succeed(); + } + + @GameTest(structure = "forge:empty11x3x3") // (10 dirt + 2 nylium + 3 extra) * 2 plants + public void fungus(GameTestHelper helper) { + // FUNGUS PlantType self explanatory + var plants = List.of(WARPED_FUNGUS, CRIMSON_FUNGUS); + + var nylium = List.of(CRIMSON_NYLIUM, WARPED_NYLIUM); + testTag(helper, BlockTags.NYLIUM, nylium); + + var extra = List.of(FARMLAND, MYCELIUM, SOUL_SOIL); + + // See FungusBlock.mayPlaceOn + var pos = iterate(helper, plants, dirt(helper)); + pos = iterate(helper, pos, plants, nylium); + pos = iterate(helper, pos, plants, extra); + helper.succeed(); + } + + // 6 on X to allow Dark area, and 2 extra rows to test the Tag + // 4 on Y to allow 2 spaces in the dark for the bad test (FLOOR GLASS PLANT CEILING) + // 4 on Z to allow 2 spaces in the dark area to test good and bad (WALL GOOD BAD WALL) + @GameTest(structure = "forge:empty16x4x16") + public void mushroom(GameTestHelper helper) { + // MushroomBlock = (SOLID && Light < 13) || Tag.MUSHROOM_GROW_BLOCK + var plants = plants(MushroomBlock.class); + + helper.makeFloor(STONE, 0); + helper.makeFloor(STONE, 3); + // I would rather make a enclosed box, but lighting doesn't propagate for some reason. + + + var soil = testTag(helper, BlockTags.MUSHROOM_GROW_BLOCK, TAGS.get(BlockTags.MUSHROOM_GROW_BLOCK)); + iterate(helper, plants, soil); + + // delay a handful of ticks to propagate lighting + helper.runAfterDelay(10, () -> { + + // Box us in so that it is dark + var dark = new BlockPos(7, 1, 7); + + for (var plant : plants) { + // Its on STONE, should survive + canSurvive(helper, dark, plant); + var bad = dark.south(); + + // Should fail on GLASS because not solid + helper.setBlock(bad, GLASS); + helper.setAndAssertBlock(bad.above(), plant); + var state = helper.getBlockState(bad.above()); + helper.assertFalse(state.canSurvive(helper.getLevel(), helper.absolutePos(bad.above())), () -> name(state) + " can survive on " + name(helper.getBlockState(bad))); + + dark = dark.east(2); + } + + helper.succeed(); + }); + } + + + //=================================================== + // HELPERS + //=================================================== + private static BlockPos iterate(GameTestHelper helper, List plants, List soils) { + return iterate(helper, BlockPos.ZERO.west(), plants, soils); + } + + private static BlockPos iterate(GameTestHelper helper, BlockPos pos, List plants, List soils) { + for (var plant : plants) { + for (var soil : soils) { + pos = offset(helper, pos); + var soilPos = pos.above(); + var plantPos = soilPos.above(); + + helper.setBlock(pos, STONE); + helper.setAndAssertBlock(soilPos, soil); + canSurvive(helper, plantPos, plant); + } + } + return pos; + } + + private static List dirt(GameTestHelper helper) { + return testTag(helper, BlockTags.DIRT, TAGS.get(BlockTags.DIRT)); + } + + private static List dirt(GameTestHelper helper, Block... extra) { + return Streams.concat(dirt(helper).stream(), Stream.of(extra)).distinct().toList(); + } + + private static List sand(GameTestHelper helper) { + return testTag(helper, BlockTags.SAND, TAGS.get(BlockTags.SAND)); + } + + private static List sandAndDirt(GameTestHelper helper, Block... extra) { + return Streams.concat(sand(helper).stream(), dirt(helper).stream(), Stream.of(extra)).distinct().toList(); + } + + // See VegitationBlock.mayPlaceOn + private static List vegitationSoils(GameTestHelper helper, Block... extra) { + return Streams.concat(dirt(helper, FARMLAND).stream(), Stream.of(extra)).distinct().toList(); + } + + private static BlockPos offset(GameTestHelper helper, BlockPos pos) { + pos = pos.east(); + if (pos.getX() >= (int)helper.getBounds().getXsize()) + pos = new BlockPos(0, pos.getY(), pos.getZ() + 1); + if (pos.getZ() >= (int)helper.getBounds().getZsize()) + helper.fail("Ran out of space to run test"); + return pos; + } + + private static List sort(List blocks) { + var sorted = new ArrayList<>(blocks); + Collections.sort(sorted, (a, b) -> name(a).compareTo(name(b))); + return sorted; + } + + private static String name(BlockState block) { + return name(block.getBlock()); + } + + private static String name(Block block) { + return BuiltInRegistries.BLOCK.getKey(block).getPath(); + } + + public static void canSurvive(GameTestHelper helper, BlockPos pos, Block expected) { + helper.setAndAssertBlock(pos, expected); + var state = helper.getBlockState(pos); + var soil = helper.getBlockState(pos.below()); + helper.assertTrue(state.canSurvive(helper.getLevel(), helper.absolutePos(pos)), () -> name(state) + " can not survive on " + name(soil)); + } + + private static List testTag(GameTestHelper helper, TagKey tag, List expected) { + var lookup = helper.registryLookup(Registries.BLOCK); + var values = lookup.getOrThrow(tag).stream() + .filter(h -> h.unwrapKey().orElseThrow().location().getNamespace().equals("minecraft")) // Only vanilla entries + .map(Holder::get) + .toList(); + helper.assertValueEqual(sort(expected), sort(values), tag.location().toString(), "Tag does not have expected values"); + return expected; + } + + @SuppressWarnings("unused") + private static void box(GameTestHelper helper, Block block, BlockPos start, BlockPos end) { + for (var pos : BlockPos.betweenClosed(start, end)) { + if ( pos.getX() == start.getX() + || pos.getX() == end.getX() + || pos.getZ() == start.getZ() + || pos.getZ() == end.getZ() + || pos.getY() == start.getY() + || pos.getY() == end.getY()) + helper.setBlock(pos, block); + } + } +} diff --git a/src/test/java/net/minecraftforge/test/BaseTestMod.java b/src/test/java/net/minecraftforge/test/BaseTestMod.java index 8f820f7ef3..10bd1d907a 100644 --- a/src/test/java/net/minecraftforge/test/BaseTestMod.java +++ b/src/test/java/net/minecraftforge/test/BaseTestMod.java @@ -48,7 +48,7 @@ public abstract class BaseTestMod { private List> testItems = new ArrayList<>(); protected final IEventBus modBus; - private final List>, DeferredRegisterData>> dataRegistries = new ArrayList<>(); + protected final List>, DeferredRegisterData>> dataRegistries = new ArrayList<>(); protected final Set> myDataRegistries = new HashSet<>(); protected final Map tests; @@ -182,7 +182,7 @@ public abstract class BaseTestMod { }); } - private void registerTestFunctions(RegisterEvent event) { + protected void registerTestFunctions(RegisterEvent event) { if (event.getRegistryKey() != Registries.TEST_FUNCTION) return;