diff --git a/gradle.properties b/gradle.properties index 5af229140..35b89c01a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,7 @@ org.gradle.caching=true # Dependencies cyclopscore_version=1.26.2-808 -integrateddynamics_version=1.30.3-1469 +integrateddynamics_version=1.32.0-1630 integratedterminalscompat_version=1.0.0-136 integratedcrafting_version=1.4.1-442 commoncapabilities_version=2.9.12-263 diff --git a/src/main/java/org/cyclops/integratedterminals/gametest/GameTestAdvancementsIntegratedTerminals.java b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestAdvancementsIntegratedTerminals.java new file mode 100644 index 000000000..e02ceb744 --- /dev/null +++ b/src/main/java/org/cyclops/integratedterminals/gametest/GameTestAdvancementsIntegratedTerminals.java @@ -0,0 +1,253 @@ +package org.cyclops.integratedterminals.gametest; + +import com.mojang.authlib.GameProfile; +import io.netty.channel.embedded.EmbeddedChannel; +import net.minecraft.advancements.AdvancementHolder; +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.GameTestHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.server.network.CommonListenerCookie; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.ServerAdvancementManager; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.entity.player.PlayerContainerEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; +import org.cyclops.integrateddynamics.RegistryEntries; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.variable.IValueType; +import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable; +import org.cyclops.integrateddynamics.api.evaluate.variable.IVariableInvalidateListener; +import org.cyclops.integrateddynamics.api.network.INetwork; +import org.cyclops.integrateddynamics.api.part.PartPos; +import org.cyclops.integrateddynamics.api.part.PartTarget; +import org.cyclops.integrateddynamics.core.evaluate.operator.Operators; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeOperator; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes; +import org.cyclops.integrateddynamics.core.helper.NetworkHelpers; +import org.cyclops.integrateddynamics.core.helper.PartHelpers; +import org.cyclops.integratedterminals.Reference; +import org.cyclops.integratedterminals.inventory.container.ContainerTerminalStoragePart; +import org.cyclops.integratedterminals.inventory.container.TerminalStorageState; +import org.cyclops.integratedterminals.part.PartTypes; + +import java.util.Optional; +import java.util.UUID; + +/** + * Game tests for all advancements in IntegratedTerminals. + * @author rubensworks + */ +@GameTestHolder(Reference.MOD_ID) +@PrefixGameTestTemplate(false) +public class GameTestAdvancementsIntegratedTerminals { + + private static boolean hasAdvancementUnlocked(ServerPlayer player, String advancementId) { + ResourceLocation id = ResourceLocation.parse(advancementId); + ServerAdvancementManager manager = player.server.getAdvancements(); + AdvancementHolder holder = manager.get(id); + if (holder == null) { + return false; + } + return player.server.getPlayerList().getPlayerAdvancements(player).getOrStartProgress(holder).isDone(); + } + + /** + * Creates a {@link ContainerTerminalStoragePart} that suppresses {@code cyclopscore:value_notify} + * packet sends. This is needed in game tests where the embedded network channel does not have + * custom NeoForge channels negotiated, causing {@link UnsupportedOperationException} whenever + * {@link org.cyclops.cyclopscore.inventory.container.ContainerExtended#setValue} tries to send + * a packet to the client. + * + *

Because Java uses virtual dispatch even during superclass construction, overriding + * {@code setValue} here also prevents the packet sends that happen inside the + * {@link ContainerTerminalStoragePart} constructor (e.g. from {@code setSelectedChannel}). + */ + private static ContainerTerminalStoragePart createSilentContainer(ServerPlayer player, PartTarget partTarget) { + return new ContainerTerminalStoragePart( + 1, player.getInventory(), partTarget, PartTypes.TERMINAL_STORAGE, + Optional.empty(), new TerminalStorageState(() -> {})) { + @Override + public void setValue(int valueId, CompoundTag value) { + // Suppress cyclopscore:value_notify packet sends in the game test environment. + } + }; + } + + /** + * Creates a mock server player in the level. + * + *

This replicates {@link GameTestHelper#makeMockServerPlayerInLevel()} but catches the + * {@link UnsupportedOperationException} that IntegratedDynamics throws when it tries to send + * an {@code integrateddynamics:all_labels} packet during the player login event, because the + * game test environment's embedded channel does not have custom NeoForge channels negotiated. + * By the time that exception fires, {@code placeNewPlayer} has already fully set up the player: + * {@link ServerPlayer#connection} is assigned, {@link ServerPlayer#initInventoryMenu()} has + * been called (enabling the {@code containerListener} that fires advancement criteria), and the + * player has been added to both the world and the player list. + */ + private static ServerPlayer createMockServerPlayer(GameTestHelper helper) { + CommonListenerCookie cookie = CommonListenerCookie.createInitial( + new GameProfile(UUID.randomUUID(), "test-mock-player"), false); + ServerPlayer player = new ServerPlayer( + helper.getLevel().getServer(), helper.getLevel(), cookie.gameProfile(), cookie.clientInformation()) { + @Override + public boolean isSpectator() { + return false; + } + + @Override + public boolean isCreative() { + return true; + } + }; + Connection connection = new Connection(PacketFlow.SERVERBOUND); + new EmbeddedChannel(connection); + try { + helper.getLevel().getServer().getPlayerList().placeNewPlayer(connection, player, cookie); + } catch (Exception ignored) { + // IntegratedDynamics sends a custom packet (integrateddynamics:all_labels) during the + // PlayerLoggedInEvent that fails in game tests because no channel negotiation occurs. + // Everything needed for advancement tests is set up before that event fires. + } + return player; + } + + /** + * Tests the root advancement, triggered by having a part_display_panel in the inventory. + */ + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testAdvancementRoot(GameTestHelper helper) { + ServerPlayer player = createMockServerPlayer(helper); + // Give player a part_display_panel to trigger the inventory_changed criterion + player.getInventory().setItem(0, new ItemStack( + BuiltInRegistries.ITEM.get(ResourceLocation.parse("integrateddynamics:part_display_panel")))); + player.inventoryMenu.broadcastChanges(); + helper.succeedWhen(() -> helper.assertTrue( + hasAdvancementUnlocked(player, "integratedterminals:root"), + "root advancement not unlocked")); + } + + /** + * Tests the menril_glass advancement, triggered by having menril_glass in the inventory. + */ + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testAdvancementMenrilGlass(GameTestHelper helper) { + ServerPlayer player = createMockServerPlayer(helper); + // Give player menril_glass to trigger the inventory_changed criterion + player.getInventory().setItem(0, new ItemStack( + BuiltInRegistries.ITEM.get(ResourceLocation.parse("integratedterminals:menril_glass")))); + player.inventoryMenu.broadcastChanges(); + helper.succeedWhen(() -> helper.assertTrue( + hasAdvancementUnlocked(player, "integratedterminals:storage_terminal/menril_glass"), + "menril_glass advancement not unlocked")); + } + + /** + * Tests the craft_storage_terminal advancement, triggered by crafting a part_terminal_storage. + */ + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testAdvancementCraftStorageTerminal(GameTestHelper helper) { + ServerPlayer player = createMockServerPlayer(helper); + // Simulate crafting a part_terminal_storage by posting the ItemCraftedEvent + ItemStack craftedItem = new ItemStack( + BuiltInRegistries.ITEM.get(ResourceLocation.parse("integratedterminals:part_terminal_storage"))); + NeoForge.EVENT_BUS.post(new PlayerEvent.ItemCraftedEvent(player, craftedItem, new SimpleContainer(9))); + helper.succeedWhen(() -> helper.assertTrue( + hasAdvancementUnlocked(player, "integratedterminals:storage_terminal/craft_storage_terminal"), + "craft_storage_terminal advancement not unlocked")); + } + + /** + * Tests the gui_storage_terminal advancement, triggered by opening the terminal storage GUI. + */ + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testAdvancementGuiStorageTerminal(GameTestHelper helper) { + BlockPos pos = new BlockPos(1, 2, 1); + ServerPlayer player = createMockServerPlayer(helper); + + // Place cable block and add terminal_storage part + helper.setBlock(pos, RegistryEntries.BLOCK_CABLE.value()); + NetworkHelpers.initNetwork(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH); + PartHelpers.addPart(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH, + PartTypes.TERMINAL_STORAGE, new ItemStack(PartTypes.TERMINAL_STORAGE.getItem())); + + // Create the container directly (not via openMenu, which triggers custom-packet sends that + // fail in the game test environment's embedded channel). Fire PlayerContainerEvent.Open to + // trigger the cyclopscore:container_gui_open advancement criterion. + PartPos partPos = PartPos.of(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH); + PartTarget partTarget = PartTarget.fromCenter(partPos); + ContainerTerminalStoragePart container = createSilentContainer(player, partTarget); + NeoForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, container)); + + helper.succeedWhen(() -> helper.assertTrue( + hasAdvancementUnlocked(player, "integratedterminals:storage_terminal/gui_storage_terminal"), + "gui_storage_terminal advancement not unlocked")); + } + + /** + * Tests the filter_enchantable advancement, triggered by setting an itemstack_enchantable operator variable + * in the terminal storage filter slots. + */ + @GameTest(template = "empty", templateNamespace = "cyclopscore") + public void testAdvancementFilterEnchantable(GameTestHelper helper) { + BlockPos pos = new BlockPos(1, 2, 1); + ServerPlayer player = createMockServerPlayer(helper); + + // Place cable block and add terminal_storage part + helper.setBlock(pos, RegistryEntries.BLOCK_CABLE.value()); + NetworkHelpers.initNetwork(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH); + PartHelpers.addPart(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH, + PartTypes.TERMINAL_STORAGE, new ItemStack(PartTypes.TERMINAL_STORAGE.getItem())); + + // Create the container directly (not via openMenu, which triggers custom-packet sends that + // fail in the game test environment's embedded channel). + PartPos partPos = PartPos.of(helper.getLevel(), helper.absolutePos(pos), Direction.NORTH); + PartTarget partTarget = PartTarget.fromCenter(partPos); + ContainerTerminalStoragePart container = createSilentContainer(player, partTarget); + + // Directly trigger the advancement by calling onVariableContentsUpdated with a mock + // IVariable that returns the itemstack_enchantable operator value. This simulates what + // happens when a valid operator variable is placed in the filter slot and the network + // evaluates it — bypassing the InventoryVariableEvaluator which returns null in game + // tests because the variable item is not registered in the network's variable store. + INetwork network = container.getNetwork().get(); + ValueTypeOperator.ValueOperator operatorValue = + ValueTypeOperator.ValueOperator.of(Operators.OBJECT_ITEMSTACK_ISENCHANTABLE); + IVariable mockVariable = new IVariable<>() { + @Override + public IValueType getType() { + return ValueTypes.OPERATOR; + } + + @Override + public ValueTypeOperator.ValueOperator getValue() throws EvaluationException { + return operatorValue; + } + + @Override + public void addInvalidationListener(IVariableInvalidateListener listener) {} + + @Override + public void removeInvalidationListener(IVariableInvalidateListener listener) {} + + @Override + public void invalidate() {} + }; + container.onVariableContentsUpdated(network, mockVariable); + + helper.succeedWhen(() -> helper.assertTrue( + hasAdvancementUnlocked(player, "integratedterminals:storage_terminal_filtering/filter_enchantable"), + "filter_enchantable advancement not unlocked")); + } + +}