diff --git a/README.md b/README.md index d5ea713f..572b662e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ Build with `./gradlew build` (Windows: `gradlew.bat build`); Python 3 is used by nine release-bytecode/outline regression checks. Dependencies resolve from public Maven repositories. The `portBridgeTest` suite also runs as part of `check` and is not shipped in the mod jar. +Forge, Minecraft and AE2 are required. Every compiled mod integration declares a +minimum and compatible version range in `src/main/resources/META-INF/mods.toml`; +these integrations remain optional, so an absent integration does not prevent startup. +Minimum versions follow the actual mod metadata (for example, Mekanism `10.4.15`, +not the jar filename's build suffix). Ore Generator samples accept Forge's `ores` +and `raw_materials` item tags, plus the equivalent `c:` datapack tags. + Optional integrations without a Forge implementation are excluded explicitly in `gradle/forge1201-pack-profile.json`; this is not a universal backport of every NeoForge integration. Extended Crafting compiles against official 6.0.10; the diff --git a/build.gradle b/build.gradle index eb6e1938..6ff6b6a0 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,9 @@ dependencies { modCompileOnly 'maven.modrinth:rxYaglEe:orFPvySe' // advancedae-1.2.7-1.20.1 modCompileOnly 'maven.modrinth:rqFWfVlz:tT3CLYvK' // apotheosis-1.20.1-7.4.8 modCompileOnly 'maven.modrinth:IiATswDj:d9GM663Q' // applied-mekanistics-1.4.2 - modCompileOnly 'maven.modrinth:XxWD5pD3:Zfyso1sP' // appliedenergistics2-forge-15.4.8 + modCompileOnly 'maven.modrinth:XxWD5pD3:jYgSnxye' // appliedenergistics2-forge-15.4.11 + modCompileOnly 'maven.modrinth:udZtKfzP:Qm3GW128' // neoecoae-20.4.2-forge + modCompileOnly 'maven.modrinth:B1CBVXHX:YSOP0G6O' // ldlib-forge-1.20.1-1.0.52.a modCompileOnly 'curse.maven:applied-flux-965012:6755986' // appliedflux-1.20-1.3.2-forge modCompileOnly 'maven.modrinth:lhGA9TYQ:1MKTLiiG' // architectury-9.2.14-forge modCompileOnly 'maven.modrinth:uy4Cnpcm:tNwsir0K' // bookshelf-forge-1.20.1-20.2.13 @@ -54,7 +56,7 @@ dependencies { modCompileOnly 'maven.modrinth:49ZofO4f:1na1IiE8' // enderio-1.20.1-6.2.13-beta-all modCompileOnly 'curse.maven:ex-deorum-901420:6614784' // exdeorum-1.46 modCompileOnly 'curse.maven:extendedae-892005:7005572' // extendedae-1.20-1.4.6-forge - modCompileOnly 'maven.modrinth:5JMG1gql:NtEZsGjO' // extendedcrafting-1.20.1-6.1.5u + modCompileOnly 'maven.modrinth:5JMG1gql:NtEZsGjO' // extendedcrafting-1.20.1-6.0.10 (tier-5 pack extension resolved at runtime) modCompileOnly 'curse.maven:flux-networks-248020:5234697' // fluxnetworks-1.20.1-7.2.1.15 modCompileOnly 'curse.maven:ftb-chunks-314906:6431735' // ftb-chunks-forge-2001.3.6 modCompileOnly 'curse.maven:ftb-teams-404468:6130786' // ftb-teams-forge-2001.3.1 @@ -86,19 +88,23 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core:5.15.2' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'org.slf4j:slf4j-api:2.0.9' } mixin { add sourceSets.main, "${mod_id}.refmap.json" - ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json"].each { config it } + ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.compact.mixins.json"].each { config it } } tasks.named('jar') { - manifest.attributes('MixinConfigs': ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json"].join(',')) + manifest.attributes('MixinConfigs': ["${mod_id}.mixins.json", "${mod_id}.create.mixins.json", "${mod_id}.mekanism.mixins.json", "${mod_id}.jade.mixins.json", "${mod_id}.compact.mixins.json"].join(',')) } tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' options.compilerArgs += ['-Xmaxerrs', '10000'] } -tasks.named('test', Test) { useJUnitPlatform() } +tasks.named('test', Test) { + useJUnitPlatform() + classpath = classpath.filter { !it.name.startsWith('slf4j-api-1.') } +} tasks.named('processResources', ProcessResources) { inputs.property 'mod_version', mod_version filesMatching('META-INF/mods.toml') { diff --git a/gradle/forge1201-pack-profile.gradle b/gradle/forge1201-pack-profile.gradle index 8c40efa0..08285690 100644 --- a/gradle/forge1201-pack-profile.gradle +++ b/gradle/forge1201-pack-profile.gradle @@ -1,11 +1,11 @@ // Optional integrations without a Forge 1.20.1 implementation remain outside this build. def profile = new groovy.json.JsonSlurper().parse(file('gradle/forge1201-pack-profile.json')) sourceSets.main.java { profile.excludes.each { exclude it } } +sourceSets.test.java { + exclude 'com/sorrowmist/useless/compat/neoecoae/NeoEcoPlanningCompatTest.java' +} tasks.named('processResources', ProcessResources) { exclude 'META-INF/neoforge.mods.toml', 'META-INF/enumextensions.json' // These blocks are not registered without NeoEcoAE in the target profile. exclude 'data/useless_mod/loot_tables/blocks/useless_compact_*.json' } - -// These tests require the NeoForge-only NeoEcoAE integration excluded above. -sourceSets.test.java.exclude 'com/sorrowmist/useless/compat/neoecoae/**' diff --git a/gradle/forge1201-pack-profile.json b/gradle/forge1201-pack-profile.json index 3c4d8479..c748df8c 100644 --- a/gradle/forge1201-pack-profile.json +++ b/gradle/forge1201-pack-profile.json @@ -46,7 +46,10 @@ "com/sorrowmist/useless/compat/lychee/**", "com/sorrowmist/useless/compat/mekanismmoremachine/**", "com/sorrowmist/useless/compat/modernindustrialization/**", - "com/sorrowmist/useless/compat/neoecoae/**", + "com/sorrowmist/useless/compat/neoecoae/NeoEcoCompat.java", + "com/sorrowmist/useless/compat/neoecoae/NeoEcoDynamicJobAttachment.java", + "com/sorrowmist/useless/compat/neoecoae/NeoEcoDynamicOutputCompat.java", + "com/sorrowmist/useless/compat/neoecoae/NeoEcoPlanningCompat.java", "com/sorrowmist/useless/compat/neovitae/**", "com/sorrowmist/useless/compat/nomadsdelight/**", "com/sorrowmist/useless/compat/occultism/**", @@ -60,7 +63,6 @@ "com/sorrowmist/useless/mixin/malum/**", "com/sorrowmist/useless/mixin/itemobliterator/**", "com/sorrowmist/useless/mixin/pneumaticcraft/**", - "com/sorrowmist/useless/mixin/compact/**", "com/sorrowmist/useless/datagen/**", "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/InfuserRecipeAdapter.java", "com/sorrowmist/useless/content/recipe/adapters/industrialforegoing/PrecisionShapedRecipeAdapter.java", diff --git a/gradle/port-bridge-tests.gradle b/gradle/port-bridge-tests.gradle index 9f5f97eb..91270a1f 100644 --- a/gradle/port-bridge-tests.gradle +++ b/gradle/port-bridge-tests.gradle @@ -46,6 +46,8 @@ sourceSets { java.include 'com/sorrowmist/useless/api/recipe/IRecipeAdapter.java' java.include 'com/sorrowmist/useless/content/recipe/IRecipeAdapter.java' java.include 'com/sorrowmist/useless/content/blockentities/HighStackItemStackHandler.java' + java.include 'com/sorrowmist/useless/content/blockentities/RecoverableItemStackHandler.java' + java.include 'com/sorrowmist/useless/content/blockentities/OreGeneratorSampleHandler.java' java.include 'com/sorrowmist/useless/api/enums/AlloyFurnaceMode.java' java.include 'com/sorrowmist/useless/content/recipe/AdvancedAlloyFurnaceRecipe.java' java.include 'com/sorrowmist/useless/content/recipe/AdvancedAlloyFurnaceRecipeSerializer.java' @@ -96,6 +98,10 @@ tasks.register('portBridgeTest', Test) { classpath = sourceSets.portBridgeTest.runtimeClasspath useJUnitPlatform() maxHeapSize = '1G' + inputs.file 'src/main/resources/META-INF/mods.toml' + doFirst { + systemProperty 'uselessMod.compileClasspath', sourceSets.main.compileClasspath.asPath + } } // ModDev's test logging adapter requires SLF4J 2; this does not affect runtime dependencies. diff --git a/src/main/java/com/sorrowmist/useless/UselessMod.java b/src/main/java/com/sorrowmist/useless/UselessMod.java index 7d0aa6ea..1dcbcb2d 100644 --- a/src/main/java/com/sorrowmist/useless/UselessMod.java +++ b/src/main/java/com/sorrowmist/useless/UselessMod.java @@ -89,8 +89,9 @@ public UselessMod() { // 无用型紧凑 C9/F9/L9:只在 ECO 存在时注册。 // 这里的类加载是条件性的——ECO 缺席时 NeoEcoCompactRegistry 永远不会被初始化。 - if (ModList.get().isLoaded("neoecoae")) { - OptionalIntegration.call("neoecoae", "com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry", + String compactEcoRegistry = "com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry"; + if (ModList.get().isLoaded("neoecoae") && OptionalIntegration.hasClass(compactEcoRegistry)) { + OptionalIntegration.call("neoecoae", compactEcoRegistry, "init", Void.class, null, new Class[]{IEventBus.class}, modEventBus); } @@ -111,8 +112,9 @@ public static ResourceLocation id(String path) { private void commonSetup(FMLCommonSetupEvent event) { RecipeAdapterCompatRegistry.init(event); - if (ModList.get().isLoaded("neoecoae")) { - OptionalIntegration.call("neoecoae", "com.sorrowmist.useless.compat.neoecoae.NeoEcoCompat", + String ecoCompat = "com.sorrowmist.useless.compat.neoecoae.NeoEcoCompat"; + if (ModList.get().isLoaded("neoecoae") && OptionalIntegration.hasClass(ecoCompat)) { + OptionalIntegration.call("neoecoae", ecoCompat, "initialize", Void.class, null, new Class[]{}); } } diff --git a/src/main/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegration.java b/src/main/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegration.java index 99cd5bf4..4715ac66 100644 --- a/src/main/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegration.java +++ b/src/main/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegration.java @@ -6,6 +6,12 @@ /** Optional source boundaries: no class linkage when the corresponding mod is absent. */ public final class OptionalIntegration { private OptionalIntegration() {} + + public static boolean hasClass(String className) { + return OptionalIntegration.class.getClassLoader() + .getResource(className.replace('.', '/') + ".class") != null; + } + public static T call(String modId,String className,String method,Class resultType,T absent, Class[] signature,Object... arguments) { if (!ModList.get().isLoaded(modId)) return absent; diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/NeoEcoCompactRegistry.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/NeoEcoCompactRegistry.java index aeef2a23..f09e79e9 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/NeoEcoCompactRegistry.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/NeoEcoCompactRegistry.java @@ -5,7 +5,6 @@ import appeng.block.AEBaseEntityBlock; import appeng.blockentity.AEBaseBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.computation.ECOComputationSystemBlockEntity; -import cn.dancingsnow.neoecoae.blocks.entity.ECOMachineInterfaceBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingSystemBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.storage.ECOStorageSystemBlockEntity; @@ -32,7 +31,6 @@ import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowCraftingWorkerBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowFluidInputHatchBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowFluidOutputHatchBlockEntity; -import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowInterfaceBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowPatternBusBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowThreadingCoreBlockEntity; import net.minecraft.core.BlockPos; @@ -208,9 +206,7 @@ public static void init(IEventBus modEventBus) { CompactMachineClassMap.register(CompactC9BlockEntity.class, ECOComputationSystemBlockEntity.class); CompactMachineClassMap.register(CompactF9BlockEntity.class, ECOCraftingSystemBlockEntity.class); CompactMachineClassMap.register(CompactL9BlockEntity.class, ECOStorageSystemBlockEntity.class); - // 影子接口与聚合总线也各自带着「假身份」的网格节点:ECO 的界面与样板终端是按 - // ECOMachineInterfaceBlockEntity / ECOCraftingPatternBusBlockEntity 这两个类去查网格的。 - CompactMachineClassMap.register(ShadowInterfaceBlockEntity.class, ECOMachineInterfaceBlockEntity.class); + // ECO queries pattern buses by their native block-entity type in the grid index. CompactMachineClassMap.register(ExposedPatternBusBlockEntity.class, ECOCraftingPatternBusBlockEntity.class); BLOCKS.register(modEventBus); diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactC9Block.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactC9Block.java index f5a70d6d..fa30b4cc 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactC9Block.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactC9Block.java @@ -1,51 +1,15 @@ package com.sorrowmist.useless.compat.neoecoae.compact.block; import cn.dancingsnow.neoecoae.blocks.computation.ECOComputationSystem; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.phys.BlockHitResult; /** * 无用型紧凑 C9 的方块形态。 * - *

直接继承 ECO 的计算系统主机方块,于是「右键开 GUI」「玩家距离校验」「掉落物带设置」 - * 这些行为全部沿用 ECO 的实现,本类只提供一个自己的方块实体。

+ *

直接继承 ECO 的计算系统主机方块,复用 ECO 20.4.x 的 LDLib 原生界面入口。

*/ public class CompactC9Block extends ECOComputationSystem { public CompactC9Block(Properties properties) { super(properties); - registerDefaultState(defaultBlockState().setValue(CompactInterfaceAccess.INTERFACE, false)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(CompactInterfaceAccess.INTERFACE); - } - - @Override - protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, - BlockHitResult hitResult) { - if (!CompactInterfaceAccess.isPlayerCloseEnough(this, level, pos, player)) { - return InteractionResult.FAIL; - } - if (player.isShiftKeyDown()) { - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.open(this, state, pos, serverPlayer); - } - return InteractionResult.sidedSuccess(level.isClientSide); - } - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.openMain(this, state, pos, serverPlayer); - return InteractionResult.CONSUME; - } - return InteractionResult.SUCCESS; } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactF9Block.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactF9Block.java index c5196cbc..e444346e 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactF9Block.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactF9Block.java @@ -6,24 +6,22 @@ import com.sorrowmist.useless.compat.neoecoae.compact.entity.CompactF9BlockEntity; import com.sorrowmist.useless.core.component.ExternalInventoryReference; import com.sorrowmist.useless.core.component.UComponents; +import com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess; +import com.sorrowmist.useless.compat.forge1201.component.ComponentAccess; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; -import net.minecraft.world.ItemInteractionResult; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; -import net.minecraftforge.common.capabilities.ForgeCapabilities; import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.common.capabilities.ForgeCapabilities; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -31,7 +29,7 @@ /** * 无用型紧凑 F9 的方块形态。 * - *

直接继承 ECO 的合成系统主机方块,交互与界面沿用 ECO 实现; + *

直接继承 ECO 的合成系统主机方块,常规交互与界面沿用 ECO 实现; * 额外补一个「手持编码样板右键」入口——紧凑方块内部没有可交互的样板总线方块, * 所以给玩家一条把样板送进 ECO 样板总线的路。

* @@ -42,66 +40,41 @@ public class CompactF9Block extends ECOCraftingSystem { public CompactF9Block(Properties properties) { super(properties); - registerDefaultState(defaultBlockState().setValue(CompactInterfaceAccess.INTERFACE, false)); } @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(CompactInterfaceAccess.INTERFACE); - } - - @Override - protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, - BlockHitResult hitResult) { - if (!CompactInterfaceAccess.isPlayerCloseEnough(this, level, pos, player)) { - return InteractionResult.FAIL; - } - if (player.isShiftKeyDown()) { - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.open(this, state, pos, serverPlayer); - } - return InteractionResult.sidedSuccess(level.isClientSide); - } - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.openMain(this, state, pos, serverPlayer); - return InteractionResult.CONSUME; - } - return InteractionResult.SUCCESS; - } - - @Override - protected ItemInteractionResult useItemOn(ItemStack heldItem, BlockState state, Level level, BlockPos pos, - Player player, InteractionHand hand, BlockHitResult hit) { + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, + InteractionHand hand, BlockHitResult hit) { + ItemStack heldItem = player.getItemInHand(hand); if (isEncodedPattern(heldItem, level) && level.getBlockEntity(pos) instanceof CompactF9BlockEntity compact) { if (level.isClientSide) { - return ItemInteractionResult.SUCCESS; + return InteractionResult.SUCCESS; } ECOPatternInsertionResult result = compact.insertPatternFromPlayerWithResult(heldItem); if (result == ECOPatternInsertionResult.INSERTED) { if (!player.isCreative()) { heldItem.shrink(1); } - return ItemInteractionResult.sidedSuccess(false); + return InteractionResult.CONSUME; } player.displayClientMessage(Component.translatable( "gui.useless_mod.compact.f9.pattern_result." + result.name().toLowerCase()), true); - return ItemInteractionResult.sidedSuccess(false); + return InteractionResult.CONSUME; } if (level.getBlockEntity(pos) instanceof CompactF9BlockEntity compact && isFluidContainer(heldItem)) { if (level.isClientSide) { - return ItemInteractionResult.SUCCESS; + return InteractionResult.SUCCESS; } // Output extraction and normal input filling use the public capability. The // second pass is a player-only recovery path for fluid left in the input pool. if (FluidUtil.interactWithFluidHandler(player, hand, compact.getFluidHandler()) || FluidUtil.interactWithFluidHandler(player, hand, compact.getInputRecoveryHandler())) { - return ItemInteractionResult.sidedSuccess(false); + return InteractionResult.CONSUME; } } - return super.useItemOn(heldItem, state, level, pos, player, hand, hit); + return super.use(state, level, pos, player, hand, hit); } /** 客户端也要能判断,否则会在服务端吞掉右键后与客户端表现不一致。 */ @@ -111,16 +84,10 @@ private static boolean isEncodedPattern(ItemStack stack, Level level) { private static boolean isFluidContainer(ItemStack stack) { return !stack.isEmpty() - && (com.sorrowmist.useless.compat.forge1201.capability.CapabilityAccess.get(stack, ForgeCapabilities.FLUID_HANDLER_ITEM) != null + && (CapabilityAccess.get(stack, ForgeCapabilities.FLUID_HANDLER_ITEM) != null || FluidUtil.getFluidContained(stack).isPresent()); } - // ------------------------------------------------ 外置样板数据的搬运 - - /** - * 拆掉方块时先落盘再解除占用。必须在 {@code super.onRemove} 之前做: - * 父类会把主机集群拆掉,影子总线就没了,那时候想序列化也来不及。 - */ @Override public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) { if (!state.is(newState.getBlock()) && !level.isClientSide @@ -130,7 +97,6 @@ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState new super.onRemove(state, level, pos, newState, movedByPiston); } - /** 掉落物带上外置存储的引用,玩家把机器挪个地方不会丢样板。 */ @Override public List getDrops(BlockState state, LootParams.Builder params) { List drops = super.getDrops(state, params); @@ -139,7 +105,7 @@ public List getDrops(BlockState state, LootParams.Builder params) { if (reference != null) { for (ItemStack drop : drops) { if (drop.is(asItem())) { - com.sorrowmist.useless.compat.forge1201.component.ComponentAccess.set(drop, UComponents.EXTERNAL_INVENTORY_REFERENCE.get(), reference); + ComponentAccess.set(drop, UComponents.EXTERNAL_INVENTORY_REFERENCE.get(), reference); break; } } @@ -149,10 +115,11 @@ public List getDrops(BlockState state, LootParams.Builder params) { } @Override - public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, + ItemStack stack) { super.setPlacedBy(level, pos, state, placer, stack); if (!level.isClientSide && level.getBlockEntity(pos) instanceof CompactF9BlockEntity compact) { - compact.adoptPatternStore(com.sorrowmist.useless.compat.forge1201.component.ComponentAccess.get(stack, UComponents.EXTERNAL_INVENTORY_REFERENCE.get())); + compact.adoptPatternStore(ComponentAccess.get(stack, UComponents.EXTERNAL_INVENTORY_REFERENCE.get())); } } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactInterfaceAccess.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactInterfaceAccess.java deleted file mode 100644 index e8a9a527..00000000 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactInterfaceAccess.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.sorrowmist.useless.compat.neoecoae.compact.block; - -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.Level; - -/** - * 紧凑方块的「潜行右键开通讯接口界面」入口。 - * - *

三个紧凑方块共用同一个布尔属性 {@link #INTERFACE}。它只活在菜单 holder 的方块状态字段里: - * 服务端打开菜单时用 {@code state.setValue(INTERFACE, true)} 造一份不下世界、不写方块的 - * 伪装状态,两端 {@code createUI} 都以 holder 里这一份状态作为唯一分支依据, - * 于是「开主机面板」与「开通讯接口界面」在双端必然同构,不存在各自读潜行状态的竞态。

- * - *

伪装的属性值不会被任何世界逻辑读取:LDLib2 的 {@code stillValid} 只比方块身份, - * ECO 的距离校验只读世界方块状态,画模型走的是世界状态。

- */ -public final class CompactInterfaceAccess { - - /** 伪装状态位;multipart 方块状态 JSON 不引用它,所以不需要改模型。 */ - public static final BooleanProperty INTERFACE = BooleanProperty.create("useless_interface"); - - private CompactInterfaceAccess() {} - - /** 菜单 holder 里的状态位:true 表示这次要开通讯接口界面而不是主机面板。 */ - public static boolean requested(BlockState state) { - return state.hasProperty(INTERFACE) && state.getValue(INTERFACE); - } - - public static boolean isPlayerCloseEnough(Block block, Level level, BlockPos pos, Player player) { - return player.level() == level - && level.getBlockState(pos).getBlock() == block - && player.distanceToSqr(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D) <= 64.0D; - } - - /** 服务端调用:用伪装状态自建 holder 并打开菜单。 */ - public static void open(BlockUIMenuType.BlockUI blockUI, BlockState state, BlockPos pos, ServerPlayer player) { - open(blockUI, state, pos, player, true); - } - - /** 服务端调用:显式打开普通主机面板,避免旧世界残留的伪装状态污染菜单分支。 */ - public static void openMain(BlockUIMenuType.BlockUI blockUI, BlockState state, BlockPos pos, ServerPlayer player) { - open(blockUI, state, pos, player, false); - } - - private static void open(BlockUIMenuType.BlockUI blockUI, BlockState state, BlockPos pos, - ServerPlayer player, boolean interfaceMode) { - player.openMenu(new BlockUIMenuType.BlockUIHolder(blockUI, player, pos, - state.setValue(INTERFACE, interfaceMode))); - } -} diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactL9Block.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactL9Block.java index ecb9f207..0a8aa8c9 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactL9Block.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/block/CompactL9Block.java @@ -1,51 +1,15 @@ package com.sorrowmist.useless.compat.neoecoae.compact.block; import cn.dancingsnow.neoecoae.blocks.storage.ECOStorageSystemBlock; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.phys.BlockHitResult; /** * 无用型紧凑 L9 的方块形态。 * - *

直接继承 ECO 的存储系统主机方块,因此「拆掉时把无限域写进掉落物、放回去时恢复」 - * 这条 ECO 原生链路对紧凑方块同样生效。

+ *

直接继承 ECO 的存储系统主机方块,复用 ECO 20.4.x 的界面与无限域掉落物链路。

*/ public class CompactL9Block extends ECOStorageSystemBlock { public CompactL9Block(Properties properties) { super(properties); - registerDefaultState(defaultBlockState().setValue(CompactInterfaceAccess.INTERFACE, false)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(CompactInterfaceAccess.INTERFACE); - } - - @Override - protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, - BlockHitResult hitResult) { - if (!CompactInterfaceAccess.isPlayerCloseEnough(this, level, pos, player)) { - return InteractionResult.FAIL; - } - if (player.isShiftKeyDown()) { - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.open(this, state, pos, serverPlayer); - } - return InteractionResult.sidedSuccess(level.isClientSide); - } - if (player instanceof ServerPlayer serverPlayer) { - CompactInterfaceAccess.openMain(this, state, pos, serverPlayer); - return InteractionResult.CONSUME; - } - return InteractionResult.SUCCESS; } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactC9BlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactC9BlockEntity.java index 4976d734..7c1e381b 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactC9BlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactC9BlockEntity.java @@ -11,26 +11,19 @@ import cn.dancingsnow.neoecoae.api.ECOTier; import cn.dancingsnow.neoecoae.blocks.entity.NEBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.computation.ECOComputationSystemBlockEntity; -import cn.dancingsnow.neoecoae.multiblock.calculator.NEComputationClusterCalculator; import cn.dancingsnow.neoecoae.multiblock.cluster.NEComputationCluster; import cn.dancingsnow.neoecoae.multiblock.cluster.NEComputationNetworkCluster; -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import com.lowdragmc.lowdraglib2.gui.ui.ModularUI; -import com.lowdragmc.lowdraglib2.syncdata.rpc.RPCSender; import com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry; import com.sorrowmist.useless.compat.neoecoae.compact.block.CompactTicker; import com.sorrowmist.useless.compat.neoecoae.compact.calculator.CompactHost; import com.sorrowmist.useless.compat.neoecoae.compact.cluster.CompactComputationCluster; -import com.sorrowmist.useless.compat.neoecoae.compact.shadow.CompactInterfaceAttachment; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowComputationDriveBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowComputationHostBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowComputationParallelCoreBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowThreadingCoreBlockEntity; -import com.sorrowmist.useless.compat.neoecoae.compact.ui.CompactSingleBlockUI; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; -import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.server.level.ServerLevel; @@ -41,7 +34,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.BitSet; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -82,10 +74,6 @@ private int buildLength() { private int cpuAnnounceAttempts; private long lastCompactTick = Long.MIN_VALUE; - /** 通讯接口界面(影子接口)的全部接线;普通右键仍然走主机自己的面板。 */ - private final CompactInterfaceAttachment compactInterface = - new CompactInterfaceAttachment<>(this, NEComputationClusterCalculator::new); - public CompactC9BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { super(type, pos, blockState, ECOTier.L9); getMainNode().setFlags(GridFlags.REQUIRE_CHANNEL); @@ -135,16 +123,6 @@ private void formCompactStructure() { compact.updateStatus(true); } - @Override - public boolean hasNormalNetworkSwitch() { - return false; - } - - @Override - public boolean hasHighEnergyNetworkSwitch() { - return true; - } - @Override public int getSelectedBuildLength() { return getMaxBuildLength(); @@ -170,7 +148,7 @@ public void tick(Level level, BlockPos pos, BlockState state) { @Override public TickingRequest getTickingRequest(IGridNode node) { - return new TickingRequest(1, 10, false); + return new TickingRequest(1, 10, false, false); } /** AE2 网格 ticker 通道:世界 ticker 万一没跑,CPU 登记在这里兜底。 */ @@ -189,7 +167,6 @@ private void compactServerTick(ServerLevel level) { return; } lastCompactTick = now; - compactInterface.tick(); if (!pendingCpuAnnounce) { return; } @@ -210,8 +187,8 @@ private void compactServerTick(ServerLevel level) { * {@code grid.getMachines(ECOComputationSystemBlockEntity.class)} 找到主机的。

*/ @Override - protected void onMainNodeGridChanged() { - super.onMainNodeGridChanged(); + public void onMainNodeStateChanged(appeng.api.networking.IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); pendingCpuAnnounce = true; cpuAnnounceAttempts = 0; } @@ -252,7 +229,6 @@ public void installCompactComponents(NEComputationCluster cluster, ServerLevel l this.compactNetworkCluster = network; this.pendingCpuAnnounce = true; - compactInterface.attach(level, cluster, getMainNode()); } private CompactComputationCluster buildShadowHost(ServerLevel level) { @@ -300,18 +276,13 @@ private void installHostHardware(NEComputationCluster cluster, ServerLevel level @SuppressWarnings({"unchecked", "rawtypes"}) private void linkAll(NEComputationCluster cluster) { - cluster.getBlockEntities().forEachRemaining(blockEntity -> { - if (blockEntity instanceof NEBlockEntity neBlockEntity) { - neBlockEntity.updateCluster(cluster); - } - }); + cluster.getBlockEntities().forEachRemaining(blockEntity -> blockEntity.updateCluster(cluster)); } private void disposeFleet() { if (level instanceof ServerLevel serverLevel && !shadowThreadingCores.isEmpty()) { pendingThreadingCoreData = captureThreadingCoreData(serverLevel.registryAccess()); } - compactInterface.release(); for (CompactComputationCluster member : fleet) { if (member != cluster && !member.isDestroyed()) { member.injectCompactNetworkCluster(null); @@ -336,7 +307,7 @@ private void restoreThreadingCoreData(HolderLookup.Provider registries) { } int count = Math.min(pendingThreadingCoreData.size(), shadowThreadingCores.size()); for (int index = 0; index < count; index++) { - shadowThreadingCores.get(index).loadTag(pendingThreadingCoreData.getCompound(index), registries); + shadowThreadingCores.get(index).loadTag(pendingThreadingCoreData.getCompound(index)); } pendingThreadingCoreData = null; } @@ -364,7 +335,6 @@ public void onChunkUnloaded() { fleet.clear(); shadowThreadingCores.clear(); compactNetworkCluster = null; - compactInterface.dispose(); super.onChunkUnloaded(); } @@ -374,32 +344,6 @@ public NEComputationNetworkCluster compactNetworkCluster() { return compactNetworkCluster; } - // ------------------------------------------------ 通讯接口界面(影子接口)入口 - - @Override - public ModularUI createUI(BlockUIMenuType.BlockUIHolder holder) { - ModularUI interfaceUI = compactInterface.createUI(holder); - return interfaceUI == null ? CompactSingleBlockUI.create(holder.player, - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.c9.title"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.c9.overview"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.c9.hardware")) - : interfaceUI; - } - - @Override - public void handleRPCPacket(RPCSender sender, byte[] data) { - if (!compactInterface.forwardRpc(sender, data)) { - super.handleRPCPacket(sender, data); - } - } - - @Override - public void handleSyncPacket(RegistryAccess access, BitSet changed, byte[] data, CompoundTag extra) { - if (!compactInterface.forwardSync(access, changed, data, extra)) { - super.handleSyncPacket(access, changed, data, extra); - } - } - @Override public void saveAdditional(CompoundTag data) { HolderLookup.Provider registries = level == null ? net.minecraft.core.RegistryAccess.EMPTY : level.registryAccess(); @@ -409,7 +353,6 @@ public void saveAdditional(CompoundTag data) { if (threadingData != null && !threadingData.isEmpty()) { data.put(SHADOW_THREADING_CORE_KEY, threadingData.copy()); } - compactInterface.save(registries, data); } @Override @@ -420,12 +363,10 @@ public void loadTag(CompoundTag data) { pendingThreadingCoreData = data.getList(SHADOW_THREADING_CORE_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND).copy(); } - compactInterface.load(registries, data); } @Override public void setRemoved() { - compactInterface.dispose(); super.setRemoved(); } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactF9BlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactF9BlockEntity.java index 3a2ba281..44f41b6b 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactF9BlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactF9BlockEntity.java @@ -10,40 +10,32 @@ import appeng.api.orientation.BlockOrientation; import cn.dancingsnow.neoecoae.api.ECOTier; import cn.dancingsnow.neoecoae.api.ECOPatternInsertionResult; -import cn.dancingsnow.neoecoae.api.me.provider.ECOBatchDispatchContext; -import cn.dancingsnow.neoecoae.api.me.provider.ECOFastPathDispatchProvider; +import cn.dancingsnow.neoecoae.api.IECOPatternStorage; import cn.dancingsnow.neoecoae.blocks.entity.NEBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowPatternBusBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingSystemBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingWorkerBlockEntity; -import cn.dancingsnow.neoecoae.multiblock.calculator.NECraftingClusterCalculator; import cn.dancingsnow.neoecoae.multiblock.cluster.NECraftingCluster; import cn.dancingsnow.neoecoae.multiblock.cluster.NECraftingNetworkCluster; -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import com.lowdragmc.lowdraglib2.gui.ui.ModularUI; -import com.lowdragmc.lowdraglib2.syncdata.rpc.RPCSender; import com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry; import com.sorrowmist.useless.compat.neoecoae.compact.block.CompactTicker; import com.sorrowmist.useless.compat.neoecoae.compact.calculator.CompactHost; import com.sorrowmist.useless.compat.neoecoae.compact.cluster.CompactCraftingCluster; import com.sorrowmist.useless.compat.neoecoae.compact.exposed.ExposedPatternBusBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.provider.CompactPatternProvider; -import com.sorrowmist.useless.compat.neoecoae.compact.shadow.CompactInterfaceAttachment; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowCraftingHostBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowCraftingParallelCoreBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowCraftingWorkerBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowFluidInputHatchBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowFluidOutputHatchBlockEntity; import com.sorrowmist.useless.compat.neoecoae.compact.shadow.ShadowNodeLink; -import com.sorrowmist.useless.compat.neoecoae.compact.ui.CompactSingleBlockUI; import com.sorrowmist.useless.core.component.ExternalInventoryKind; import com.sorrowmist.useless.core.component.ExternalInventoryReference; import com.sorrowmist.useless.world.inventory.ExternalInventoryStore; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; -import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -56,7 +48,6 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.BitSet; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -83,7 +74,7 @@ * {@link ExternalInventoryStore} 里那份 UUID 键控的外置存档中(见 {@link #PATTERN_BUS_KEY})。

*/ public class CompactF9BlockEntity extends ECOCraftingSystemBlockEntity - implements CompactHost, CompactTicker, ICraftingProvider, ECOFastPathDispatchProvider { + implements CompactHost, CompactTicker, ICraftingProvider { /** 与 ECO 自己的虚拟合成判定一致:恰好 8 台。 */ @@ -123,10 +114,6 @@ private int buildLength() { private int lastPatternContentRevision = Integer.MIN_VALUE; private long lastPatternAuxiliaryRevision = Long.MIN_VALUE; - /** 通讯接口界面(影子接口)的全部接线;普通右键仍然走主机自己的面板。 */ - private final CompactInterfaceAttachment compactInterface = - new CompactInterfaceAttachment<>(this, NECraftingClusterCalculator::new); - /** 对外代表 176 条影子总线的聚合总线;它的节点同样靠宿主节点显式接上网格。 */ @Nullable private ExposedPatternBusBlockEntity exposedBus; @@ -166,7 +153,8 @@ public CompactF9BlockEntity(BlockEntityType type, BlockPos pos, BlockState bl // ECO 的样板总线就是这么做的(总线 BE 既实现 ICraftingProvider 又实现 IECOPatternStorage), // AE2/ECO 的各种枚举都是「拿节点 → 取服务 → 和宿主比对」,独立对象会让它们认不出来。 getMainNode() - .addService(ICraftingProvider.class, this); + .addService(ICraftingProvider.class, this) + .addService(IECOPatternStorage.class, patternProvider); } // ------------------------------------------------------------- 成形生命周期 @@ -211,11 +199,6 @@ private void formCompactStructure() { compact.updateStatus(true); } - @Override - public boolean hasHighEnergyNetworkSwitch() { - return true; - } - @Override public int getSelectedBuildLength() { return getMaxBuildLength(); @@ -265,16 +248,13 @@ private void compactServerTick(ServerLevel level) { if (exposedBus != null) { exposedBus.tick(); int revision = exposedBus.getPatternContentRevision(); - long auxiliaryRevision = exposedBus.getAuxiliaryRevision(); - if (revision != lastPatternContentRevision || auxiliaryRevision != lastPatternAuxiliaryRevision) { + if (revision != lastPatternContentRevision) { lastPatternContentRevision = revision; - lastPatternAuxiliaryRevision = auxiliaryRevision; // 内容变了才需要回写外置存储;挂起一次写,等区块存盘/卸载时真正落盘。 patternStoreDirty = true; setChanged(); } } - compactInterface.tick(); // 影子主机没有独立网格 ticker,仍由 ECO 原生冷却逻辑补充各自的内部缓冲。 for (int index = 1; index < fleet.size(); index++) { fleet.get(index).getController().tickingRequest(null, 1); @@ -284,8 +264,8 @@ private void compactServerTick(ServerLevel level) { /** 主机节点入网/换网后重新把合并后的样板提供者登记给 AE2(它只在收到刷新通知时才会重读)。 */ @Override - protected void onMainNodeGridChanged() { - super.onMainNodeGridChanged(); + public void onMainNodeStateChanged(appeng.api.networking.IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); refreshPatternProvider(); } @@ -341,7 +321,7 @@ public void installCompactComponents(NECraftingCluster cluster, ServerLevel leve for (CompactCraftingCluster member : fleet) { linkAll(member); } - NECraftingNetworkCluster network = new NECraftingNetworkCluster(); + NECraftingNetworkCluster network = new NECraftingNetworkCluster(level); List members = new ArrayList<>(fleet); network.configure(members); for (CompactCraftingCluster member : fleet) { @@ -353,19 +333,13 @@ public void installCompactComponents(NECraftingCluster cluster, ServerLevel leve patternProvider.bind(shadowBuses); restorePatternBusData(level); - for (ECOCraftingPatternBusBlockEntity bus : shadowBuses) { - // 影子总线不会走 onReady,这里用 ECO 自己的刷新入口把样板索引建起来。 - bus.flushScheduledPatternDetails(); - } installExposedBus(level); // 刚恢复的内容不计为「变化」:将版本基准对齐到当前值,避免存档载入时立即触发一次无谓写入。 // (是否确实需要写入外置存储由 restorePatternBusData 判断,它可区分「迁移」与「数据本就在存储中」两种情况。) if (exposedBus != null) { lastPatternContentRevision = exposedBus.getPatternContentRevision(); - lastPatternAuxiliaryRevision = exposedBus.getAuxiliaryRevision(); } ICraftingProvider.requestUpdate(getMainNode()); - compactInterface.attach(level, cluster, getMainNode()); } @@ -394,9 +368,8 @@ private ListTag captureShadowHostData(HolderLookup.Provider registries) { ListTag saved = new ListTag(); for (int index = 1; index < fleet.size(); index++) { ECOCraftingSystemBlockEntity host = fleet.get(index).getController(); - host.getRootStorage().requireInit(); CompoundTag hostData = new CompoundTag(); - host.saveManagedPersistentData(registries, hostData, false); + host.saveAdditional(hostData); saved.add(hostData); } return saved; @@ -418,8 +391,7 @@ private void restoreShadowHostData(HolderLookup.Provider registries) { } for (int index = 1; index < fleet.size() && index <= pendingShadowHostData.size(); index++) { ECOCraftingSystemBlockEntity host = fleet.get(index).getController(); - host.getRootStorage().requireInit(); - host.loadManagedPersistentData(registries, pendingShadowHostData.getCompound(index - 1)); + host.loadTag(pendingShadowHostData.getCompound(index - 1)); } pendingShadowHostData = null; } @@ -430,7 +402,7 @@ private void restoreShadowWorkerData(HolderLookup.Provider registries) { } int count = Math.min(pendingShadowWorkerData.size(), shadowWorkers.size()); for (int index = 0; index < count; index++) { - shadowWorkers.get(index).loadTag(pendingShadowWorkerData.getCompound(index), registries); + shadowWorkers.get(index).loadTag(pendingShadowWorkerData.getCompound(index)); } pendingShadowWorkerData = null; } @@ -471,11 +443,7 @@ private void installHostHardware(NECraftingCluster cluster, ServerLevel level) { @SuppressWarnings({"unchecked", "rawtypes"}) private void linkAll(NECraftingCluster cluster) { - cluster.getBlockEntities().forEachRemaining(blockEntity -> { - if (blockEntity instanceof NEBlockEntity neBlockEntity) { - neBlockEntity.updateCluster(cluster); - } - }); + cluster.getBlockEntities().forEachRemaining(blockEntity -> blockEntity.updateCluster(cluster)); } /** 建出对外代表 176 条影子总线的聚合总线,并把它接上宿主节点。 */ @@ -511,13 +479,12 @@ private void restorePatternBusData(ServerLevel level) { int count = Math.min(saved.size(), shadowBuses.size()); for (int index = 0; index < count; index++) { ECOCraftingPatternBusBlockEntity bus = shadowBuses.get(index); - bus.getRootStorage().requireInit(); - bus.loadManagedPersistentData(level.registryAccess(), saved.getCompound(index)); + bus.loadTag(saved.getCompound(index)); for (int slot = 0; slot < bus.getPatternSlotCount(); slot++) { ItemStack stack = bus.getTerminalPatternInventory().getStackInSlot(slot); if (!stack.isEmpty()) { - bus.setPatternDirect(slot, ItemStack.EMPTY); - bus.setPatternDirect(slot, stack.copy()); + bus.itemHandler.setStackInSlot(slot, ItemStack.EMPTY); + bus.itemHandler.setStackInSlot(slot, stack.copy()); } } } @@ -540,9 +507,8 @@ private ListTag storedPatternBusData(ServerLevel level) { private ListTag capturePatternBusData(HolderLookup.Provider registries) { ListTag saved = new ListTag(); for (ECOCraftingPatternBusBlockEntity bus : shadowBuses) { - bus.getRootStorage().requireInit(); CompoundTag busData = new CompoundTag(); - bus.saveManagedPersistentData(registries, busData, false); + bus.saveAdditional(busData); saved.add(busData); } return saved; @@ -633,7 +599,6 @@ private void disposeFleet() { pendingShadowWorkerData = captureShadowWorkerData(serverLevel.registryAccess()); } } - compactInterface.release(); if (exposedBus != null) { exposedBus.onChunkUnloaded(); } @@ -665,7 +630,6 @@ public void onChunkUnloaded() { disposeFleet(); resetCollections(); compactNetworkCluster = null; - compactInterface.dispose(); super.onChunkUnloaded(); } @@ -692,11 +656,6 @@ public boolean isBusy() { return patternProvider.isBusy(); } - @Override - public Preparation eco$prepareFastPath(ECOBatchDispatchContext context) { - return patternProvider.eco$prepareFastPath(context); - } - @Override public Set getEmitableItems() { return patternProvider.getEmitableItems(); @@ -716,7 +675,7 @@ public ECOPatternInsertionResult insertPatternFromPlayerWithResult(ItemStack sta if (stack.isEmpty() || patternProvider.isEmpty()) { return ECOPatternInsertionResult.NO_TARGET; } - ECOPatternInsertionResult result = patternProvider.insertPatternWithResult(stack); + ECOPatternInsertionResult result = patternProvider.insertPattern(stack); if (result != ECOPatternInsertionResult.INSERTED) { return result; } @@ -741,37 +700,6 @@ public IFluidHandler getInputRecoveryHandler() { return ((CompactF9FluidHandler) fluidHandler).inputRecoveryHandler(); } - /** Client-side bridge for ECO's chunked pattern preview transport. */ - public void receivePatternPreview(CompoundTag payload) { - compactInterface.receivePatternPreview(payload); - } - - // ------------------------------------------------ 通讯接口界面(影子接口)入口 - - @Override - public ModularUI createUI(BlockUIMenuType.BlockUIHolder holder) { - ModularUI interfaceUI = compactInterface.createUI(holder); - return interfaceUI == null ? CompactSingleBlockUI.create(holder.player, - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.f9.title"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.f9.overview"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.f9.interaction")) - : interfaceUI; - } - - @Override - public void handleRPCPacket(RPCSender sender, byte[] data) { - if (!compactInterface.forwardRpc(sender, data)) { - super.handleRPCPacket(sender, data); - } - } - - @Override - public void handleSyncPacket(RegistryAccess access, BitSet changed, byte[] data, CompoundTag extra) { - if (!compactInterface.forwardSync(access, changed, data, extra)) { - super.handleSyncPacket(access, changed, data, extra); - } - } - @Override public void saveAdditional(CompoundTag data) { HolderLookup.Provider registries = level == null ? net.minecraft.core.RegistryAccess.EMPTY : level.registryAccess(); @@ -780,10 +708,10 @@ public void saveAdditional(CompoundTag data) { // 整包同步给客户端。这里只在内容真的变过之后,把数据写进 UUID 键控的外置存储。 flushPatternStore(false); CompoundTag inputFluid = new CompoundTag(); - fluidInputHatch.tank.writeToNBT(registries, inputFluid); + fluidInputHatch.tank.writeToNBT(inputFluid); data.put(FLUID_INPUT_KEY, inputFluid); CompoundTag outputFluid = new CompoundTag(); - fluidOutputHatch.tank.writeToNBT(registries, outputFluid); + fluidOutputHatch.tank.writeToNBT(outputFluid); data.put(FLUID_OUTPUT_KEY, outputFluid); ListTag shadowHostData = fleet.size() > 1 ? captureShadowHostData(registries) : pendingShadowHostData; if (shadowHostData != null) { @@ -794,7 +722,6 @@ public void saveAdditional(CompoundTag data) { if (shadowWorkerData != null && !shadowWorkerData.isEmpty()) { data.put(SHADOW_WORKER_KEY, shadowWorkerData.copy()); } - compactInterface.save(registries, data); } @Override @@ -802,12 +729,10 @@ public void loadTag(CompoundTag data) { HolderLookup.Provider registries = level == null ? net.minecraft.core.RegistryAccess.EMPTY : level.registryAccess(); super.loadTag(data); if (data.contains(FLUID_INPUT_KEY, Tag.TAG_COMPOUND)) { - fluidInputHatch.tank.readFromNBT(registries, - data.getCompound(FLUID_INPUT_KEY)); + fluidInputHatch.tank.readFromNBT(data.getCompound(FLUID_INPUT_KEY)); } if (data.contains(FLUID_OUTPUT_KEY, Tag.TAG_COMPOUND)) { - fluidOutputHatch.tank.readFromNBT(registries, - data.getCompound(FLUID_OUTPUT_KEY)); + fluidOutputHatch.tank.readFromNBT(data.getCompound(FLUID_OUTPUT_KEY)); } if (data.contains(SHADOW_HOST_KEY, Tag.TAG_LIST)) { pendingShadowHostData = data.getList(SHADOW_HOST_KEY, Tag.TAG_COMPOUND).copy(); @@ -821,7 +746,6 @@ public void loadTag(CompoundTag data) { pendingPatternBusData = data.getList(PATTERN_BUS_KEY, Tag.TAG_COMPOUND).copy(); patternStoreDirty = true; } - compactInterface.load(registries, data); } @Override @@ -829,7 +753,6 @@ public void setRemoved() { // 兜底:正常拆除走方块的 onRemove → releasePatternStore(),那时已经强制落盘过; // 这里只补「没走那条路就被摘掉」的情形,所以按脏标记判断,不做无谓的 MB 级重序列化。 flushPatternStore(false); - compactInterface.dispose(); super.setRemoved(); } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactL9BlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactL9BlockEntity.java index c9a8a724..3f28b27f 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactL9BlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/entity/CompactL9BlockEntity.java @@ -13,26 +13,17 @@ import cn.dancingsnow.neoecoae.blocks.entity.NEBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.storage.ECOStorageSystemBlockEntity; import cn.dancingsnow.neoecoae.impl.storage.infinite.ECOStorageHostMode; -import cn.dancingsnow.neoecoae.multiblock.calculator.NEStorageClusterCalculator; import cn.dancingsnow.neoecoae.multiblock.cluster.NEStorageCluster; -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import com.lowdragmc.lowdraglib2.gui.ui.ModularUI; -import com.lowdragmc.lowdraglib2.syncdata.rpc.RPCSender; import com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry; import com.sorrowmist.useless.compat.neoecoae.compact.block.CompactTicker; import com.sorrowmist.useless.compat.neoecoae.compact.calculator.CompactHost; import com.sorrowmist.useless.compat.neoecoae.compact.cluster.CompactStorageCluster; -import com.sorrowmist.useless.compat.neoecoae.compact.shadow.CompactInterfaceAttachment; -import com.sorrowmist.useless.compat.neoecoae.compact.ui.CompactSingleBlockUI; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; -import net.minecraft.core.RegistryAccess; -import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; @@ -41,7 +32,6 @@ import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; -import java.util.BitSet; import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -79,10 +69,6 @@ public class CompactL9BlockEntity extends ECOStorageSystemBlockEntity private int maintainTicker; private long lastCompactTick = Long.MIN_VALUE; - /** 通讯接口界面(影子接口)的全部接线;普通右键仍然走主机自己的面板。 */ - private final CompactInterfaceAttachment compactInterface = - new CompactInterfaceAttachment<>(this, NEStorageClusterCalculator::new); - public CompactL9BlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState) { super(type, pos, blockState, ECOTier.L9); @@ -132,11 +118,7 @@ private void formCompactStructure() { @SuppressWarnings({"unchecked", "rawtypes"}) private void linkAll(NEStorageCluster cluster) { - cluster.getBlockEntities().forEachRemaining(blockEntity -> { - if (blockEntity instanceof NEBlockEntity neBlockEntity) { - neBlockEntity.updateCluster(cluster); - } - }); + cluster.getBlockEntities().forEachRemaining(blockEntity -> blockEntity.updateCluster(cluster)); } // ------------------------------------------------------------------ tick @@ -151,7 +133,7 @@ public void tick(Level level, BlockPos pos, BlockState state) { @Override public TickingRequest getTickingRequest(IGridNode node) { - return new TickingRequest(1, 10, false); + return new TickingRequest(1, 10, false, false); } /** AE2 网格 ticker 通道:世界 ticker 万一没跑,存储重挂与自愈在这里兜底。 */ @@ -170,7 +152,6 @@ private void compactServerTick(ServerLevel serverLevel) { return; } lastCompactTick = now; - compactInterface.tick(); // 主机自己的 IStorageProvider 是在节点入网那一刻被 AE2 读取的,那时 formed 还是 false, // 无限存储会被跳过;所以入网后必须补一次「重挂存储」。 if (pendingStorageAnnounce && storageAnnounceAttempts++ < ANNOUNCE_MAX_ATTEMPTS) { @@ -183,8 +164,8 @@ private void compactServerTick(ServerLevel serverLevel) { } @Override - protected void onMainNodeGridChanged() { - super.onMainNodeGridChanged(); + public void onMainNodeStateChanged(appeng.api.networking.IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); // 换了网格就要重新挂存储,并重新确认无限模式。 pendingStorageAnnounce = true; storageAnnounceAttempts = 0; @@ -207,7 +188,7 @@ private boolean requestStorageUpdate() { * {@code formed_infinite},这里把它拉回来。 */ private void maintainInfiniteStorage(ServerLevel level) { - if (level == null || isFormedInfiniteMode()) { + if (level == null || isInfiniteMode()) { return; } primeInfiniteComponents(); @@ -224,7 +205,7 @@ private void maintainInfiniteStorage(ServerLevel level) { @Override - public boolean canExtractInfiniteComponents() { + public boolean canTakeInfiniteStorageComponent() { return false; } @@ -257,10 +238,8 @@ public void addAdditionalDrops(Level level, BlockPos pos, List drops) @Override public void installCompactComponents(NEStorageCluster cluster, ServerLevel level) { - compactInterface.release(); primeInfiniteComponents(); restoreOrCreateInfiniteDomain(level); - compactInterface.attach(level, cluster, getMainNode()); } /** @@ -316,12 +295,12 @@ private AppEngInternalInventory componentInventory() { private void restoreOrCreateInfiniteDomain(ServerLevel level) { ItemStack carrier = new ItemStack(NeoEcoCompactRegistry.COMPACT_L9_ITEM.get()); applyInfiniteDomainToControllerDrop(carrier); - CompoundTag tag = carrier.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag(); + CompoundTag tag = carrier.getOrCreateTag().copy(); if (!tag.hasUUID(DOMAIN_TAG)) { tag.putUUID(DOMAIN_TAG, stableDomainId(level)); } tag.putString(MODE_TAG, ECOStorageHostMode.FORMED_INFINITE.id()); - carrier.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); + carrier.getOrCreateTag().merge(tag); restoreInfiniteDomainFromItem(carrier); } @@ -330,55 +309,25 @@ private UUID stableDomainId(ServerLevel level) { return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)); } - // ------------------------------------------------ 通讯接口界面(影子接口)入口 - - @Override - public ModularUI createUI(BlockUIMenuType.BlockUIHolder holder) { - ModularUI interfaceUI = compactInterface.createUI(holder); - return interfaceUI == null ? CompactSingleBlockUI.create(holder.player, - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.l9.title"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.l9.overview"), - net.minecraft.network.chat.Component.translatable("gui.useless_mod.compact.l9.interaction")) - : interfaceUI; - } - - @Override - public void handleRPCPacket(RPCSender sender, byte[] data) { - if (!compactInterface.forwardRpc(sender, data)) { - super.handleRPCPacket(sender, data); - } - } - - @Override - public void handleSyncPacket(RegistryAccess access, BitSet changed, byte[] data, CompoundTag extra) { - if (!compactInterface.forwardSync(access, changed, data, extra)) { - super.handleSyncPacket(access, changed, data, extra); - } - } - @Override public void saveAdditional(CompoundTag data) { HolderLookup.Provider registries = level == null ? net.minecraft.core.RegistryAccess.EMPTY : level.registryAccess(); super.saveAdditional(data); - compactInterface.save(registries, data); } @Override public void loadTag(CompoundTag data) { HolderLookup.Provider registries = level == null ? net.minecraft.core.RegistryAccess.EMPTY : level.registryAccess(); super.loadTag(data); - compactInterface.load(registries, data); } @Override public void onChunkUnloaded() { - compactInterface.dispose(); super.onChunkUnloaded(); } @Override public void setRemoved() { - compactInterface.dispose(); super.setRemoved(); } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntity.java index 04ffe7a6..4d675be5 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntity.java @@ -5,13 +5,11 @@ import appeng.api.inventories.InternalInventory; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; -import appeng.api.networking.IGridNodeListener; import appeng.api.networking.IManagedGridNode; import appeng.api.networking.crafting.ICraftingProvider; import appeng.api.orientation.BlockOrientation; import appeng.api.stacks.AEItemKey; import cn.dancingsnow.neoecoae.api.ECOPatternInsertionResult; -import cn.dancingsnow.neoecoae.api.ECOPreparedPattern; import cn.dancingsnow.neoecoae.blocks.entity.ECOMachineInterfaceBlockEntity; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; import cn.dancingsnow.neoecoae.multiblock.cluster.NECraftingCluster; @@ -51,7 +49,6 @@ public class ExposedPatternBusBlockEntity extends ECOCraftingPatternBusBlockEnti private final List buses = new ArrayList<>(); private int[] busRevisions = new int[0]; private int[] busSlotCounts = new int[0]; - private long[] busAuxiliaryRevisions = new long[0]; @Nullable private InternalInventory combinedTerminalInventory; @Nullable @@ -78,11 +75,9 @@ public void bind(List source, IManagedGridNode mergedPatternInventory.bind(buses); busRevisions = new int[buses.size()]; busSlotCounts = new int[buses.size()]; - busAuxiliaryRevisions = new long[buses.size()]; for (int index = 0; index < buses.size(); index++) { - busRevisions[index] = buses.get(index).getPatternContentRevision(); + busRevisions[index] = patternContentRevision(buses.get(index)); busSlotCounts[index] = buses.get(index).getPatternSlotCount(); - busAuxiliaryRevisions[index] = buses.get(index).getAuxiliaryRevision(); } combinedTerminalInventory = null; this.hostNode = hostNode; @@ -98,20 +93,6 @@ public void joinPatternDomain(NECraftingCluster owner) { public void onReady() { } - @Override - public void onMainNodeStateChanged(IGridNodeListener.State reason) { - if (reason != IGridNodeListener.State.POWER && reason != IGridNodeListener.State.GRID_BOOT) { - return; - } - IGrid grid = getMainNode().getGrid(); - if (grid == null) { - return; - } - for (ECOMachineInterfaceBlockEntity machineInterface : grid.getActiveMachines(ECOMachineInterfaceBlockEntity.class)) { - machineInterface.onPatternBusTopologyChanged(this); - } - } - @Override public void onChunkUnloaded() { super.onChunkUnloaded(); @@ -130,40 +111,23 @@ public void setRemoved() { * 所以这里用轮询兜底,任何一条写入都不会漏掉。

*/ public void tick() { - List changedBuses = new ArrayList<>(); - boolean layoutChanged = false; + boolean changed = false; for (int index = 0; index < buses.size(); index++) { ECOCraftingPatternBusBlockEntity bus = buses.get(index); - bus.flushScheduledPatternDetails(); - int revision = bus.getPatternContentRevision(); - long auxiliaryRevision = bus.getAuxiliaryRevision(); + int revision = patternContentRevision(bus); int slotCount = bus.getPatternSlotCount(); boolean resized = busSlotCounts[index] != slotCount; - if (busRevisions[index] == revision && busAuxiliaryRevisions[index] == auxiliaryRevision && !resized) { + if (busRevisions[index] == revision && !resized) { continue; } busRevisions[index] = revision; - busAuxiliaryRevisions[index] = auxiliaryRevision; busSlotCounts[index] = slotCount; - layoutChanged |= resized; - changedBuses.add(bus); + changed = true; } - if (!changedBuses.isEmpty()) { + if (changed) { combinedTerminalInventory = null; - if (layoutChanged) { - IGrid grid = getGrid(); - if (grid != null) { - // 一条总线的页数变化会移动后面所有槽位,即使总槽数刚好没有变化。 - for (ECOMachineInterfaceBlockEntity machineInterface - : grid.getActiveMachines(ECOMachineInterfaceBlockEntity.class)) { - machineInterface.onPatternBusInventoryChanged(this); - } - } - } else { - changedBuses.forEach(this::notifyInterfacesOf); - } // 派单由宿主节点上的样板提供者负责,样板集一变就要让它重读合并结果。 - if (hostNode != null) { + if (hostNode != null && hostNode.getGrid() != null) { ICraftingProvider.requestUpdate(hostNode); } } @@ -195,52 +159,18 @@ public PatternContainerGroup getTerminalGroup() { return new PatternContainerGroup(AEItemKey.of(block.asItem()), block.getName(), List.of()); } - @Override public void setPatternDirect(int slot, ItemStack stack) { mergedPatternInventory.setItemDirect(slot, stack); } - @Override - public void beginPatternBatch() { - for (ECOCraftingPatternBusBlockEntity bus : buses) { - bus.beginPatternBatch(); - } - } - - @Override - public void endPatternBatch() { - for (ECOCraftingPatternBusBlockEntity bus : buses) { - bus.endPatternBatch(); - } - } - // ---------------------------------------------------------------- 样板写入 @Override - public boolean insertPattern(ItemStack itemStack) { - return insertPatternWithResult(itemStack) == ECOPatternInsertionResult.INSERTED; - } - - @Override - public ECOPatternInsertionResult insertPatternWithResult(ItemStack itemStack) { - ECOPreparedPattern prepared = preparePattern(itemStack); - return prepared == null ? ECOPatternInsertionResult.INCOMPATIBLE : insertPreparedPattern(prepared); - } - - @Override - public ECOPatternInsertionResult insertPatternKnownUnique(ItemStack itemStack) { - ECOPreparedPattern prepared = preparePattern(itemStack); - return prepared == null ? ECOPatternInsertionResult.INCOMPATIBLE : insertPreparedPatternKnownUnique(prepared); - } - - @Override - public ECOPatternInsertionResult insertPreparedPattern(ECOPreparedPattern prepared) { - return insertIntoBuses(bus -> bus.insertPreparedPattern(prepared)); - } - - @Override - public ECOPatternInsertionResult insertPreparedPatternKnownUnique(ECOPreparedPattern prepared) { - return insertIntoBuses(bus -> bus.insertPreparedPatternKnownUnique(prepared)); + public ECOPatternInsertionResult insertPattern(ItemStack itemStack) { + if (itemStack.isEmpty()) { + return ECOPatternInsertionResult.INCOMPATIBLE; + } + return insertIntoBuses(bus -> bus.insertPattern(itemStack)); } /** 紧凑机的样板仍然存在真实总线里,所以这里只是把插入转给第一条腾得出位置的总线。 */ @@ -257,12 +187,6 @@ private ECOPatternInsertionResult insertIntoBuses( return last; } - /** 内部总线都各自校验自己的逻辑域,去重交给样板目录做全网检查。 */ - @Override - public boolean checksLogicalDomainForDuplicates() { - return false; - } - // ---------------------------------------------------------------- 目录 / 终端读路径 @Override @@ -277,59 +201,29 @@ public boolean isBusy() { } public void refreshAdvertisedPatterns() { - refreshPatternDetailsForCatalog(); if (hostNode != null && hostNode.getGrid() != null) { ICraftingProvider.requestUpdate(hostNode); } } - /** 内容版本取各内部总线版本的滚动哈希:任何一条变了,样板目录就会重建本视图的索引。 */ - @Override + /** Content hash for the 20.4.x API, which has no pattern revision counter. */ public int getPatternContentRevision() { int revision = 1; for (ECOCraftingPatternBusBlockEntity bus : buses) { - revision = revision * 31 + bus.getPatternContentRevision(); + revision = revision * 31 + patternContentRevision(bus); } return revision; } - @Override - public void refreshPatternDetailsForCatalog() { - for (ECOCraftingPatternBusBlockEntity bus : buses) { - bus.refreshPatternDetailsForCatalog(); - } - } - - @Override - @Nullable - public IPatternDetails getDecodedPatternDetails(int slot) { - ECOCraftingPatternBusBlockEntity bus = mergedPatternInventory.busOf(slot); - return bus == null ? null : bus.getDecodedPatternDetails(mergedPatternInventory.localSlotOf(slot)); - } - - @Override - public String getPatternSearchKeywords(int slot) { - ECOCraftingPatternBusBlockEntity bus = mergedPatternInventory.busOf(slot); - return bus == null ? "" : bus.getPatternSearchKeywords(mergedPatternInventory.localSlotOf(slot)); - } - - // ---------------------------------------------------------------- 通知 - - /** 把某条内部总线的整段下标标脏(具体动了哪个槽只有那条总线自己知道)。 */ - private void notifyInterfacesOf(ECOCraftingPatternBusBlockEntity bus) { - int offset = mergedPatternInventory.offsetOf(bus); - IGrid grid = getMainNode().getGrid(); - if (offset < 0 || grid == null) { - return; - } - int count = bus.getPatternSlotCount(); - int[] slots = new int[count]; - for (int index = 0; index < count; index++) { - slots[index] = offset + index; - } - for (ECOMachineInterfaceBlockEntity machineInterface : grid.getActiveMachines(ECOMachineInterfaceBlockEntity.class)) { - machineInterface.onPatternBusInventoryChanged(this, slots); + private static int patternContentRevision(ECOCraftingPatternBusBlockEntity bus) { + int revision = bus.getPatternSlotCount(); + for (int slot = 0; slot < bus.getPatternSlotCount(); slot++) { + ItemStack stack = bus.itemHandler.getStackInSlot(slot); + int value = stack.isEmpty() ? 0 : 31 * (31 * stack.getItem().hashCode() + stack.getCount()) + + (stack.getTag() == null ? 0 : stack.getTag().hashCode()); + revision = revision * 31 + value; } + return revision; } // ---------------------------------------------------------------- 影子卫生:绝不回写世界 diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventory.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventory.java index 3ccd71c3..c19c3596 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventory.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventory.java @@ -51,7 +51,7 @@ public int size() { public ItemStack getStackInSlot(int slot) { syncOffsets(); int part = partOf(slot); - return part < 0 ? ItemStack.EMPTY : bus(part).getPatternSlotInventory().getStackInSlot(localSlot(part, slot)); + return part < 0 ? ItemStack.EMPTY : bus(part).itemHandler.getStackInSlot(localSlot(part, slot)); } @Override @@ -61,7 +61,7 @@ public void setItemDirect(int slot, ItemStack stack) { if (part < 0) { return; } - bus(part).setPatternDirect(localSlot(part, slot), stack); + bus(part).itemHandler.setStackInSlot(localSlot(part, slot), stack); syncOffsets(); } @@ -69,14 +69,14 @@ public void setItemDirect(int slot, ItemStack stack) { public boolean isItemValid(int slot, ItemStack stack) { syncOffsets(); int part = partOf(slot); - return part >= 0 && bus(part).getPatternSlotInventory().isItemValid(localSlot(part, slot), stack); + return part >= 0 && bus(part).itemHandler.isItemValid(localSlot(part, slot), stack); } @Override public int getSlotLimit(int slot) { syncOffsets(); int part = partOf(slot); - return part < 0 ? 0 : bus(part).getPatternSlotInventory().getSlotLimit(localSlot(part, slot)); + return part < 0 ? 0 : bus(part).itemHandler.getSlotLimit(localSlot(part, slot)); } /** diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CompactPatternProvider.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CompactPatternProvider.java index e8211d07..8dca7f71 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CompactPatternProvider.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CompactPatternProvider.java @@ -6,8 +6,6 @@ import appeng.api.stacks.KeyCounter; import cn.dancingsnow.neoecoae.api.ECOPatternInsertionResult; import cn.dancingsnow.neoecoae.api.IECOPatternStorage; -import cn.dancingsnow.neoecoae.api.me.provider.ECOBatchDispatchContext; -import cn.dancingsnow.neoecoae.api.me.provider.ECOFastPathDispatchProvider; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; import net.minecraft.world.item.ItemStack; @@ -22,8 +20,7 @@ *

AE2 与 ECO 的网格每个服务类只认一个实例,而紧凑 F9 内部有 176 条真实样板总线; * 这个聚合器把它们的样板集合、派单与插入接口按 ECO 原生实现逐条转发,不做任何自己的样板逻辑。

*/ -public final class CompactPatternProvider implements ICraftingProvider, IECOPatternStorage, - ECOFastPathDispatchProvider { +public final class CompactPatternProvider implements ICraftingProvider, IECOPatternStorage { private final List buses = new ArrayList<>(); @@ -84,35 +81,15 @@ public Set getEmitableItems() { return union; } - /** - * 保留 ECO 样板总线的原生批量/虚拟批次派单;普通 ICraftingProvider 转发不会进入这条路径。 - */ @Override - public Preparation eco$prepareFastPath(ECOBatchDispatchContext context) { - Preparation best = null; - for (ECOCraftingPatternBusBlockEntity bus : buses) { - Preparation candidate = bus.eco$prepareFastPath(context); - if (candidate != null && (best == null || candidate.capacity() > best.capacity())) { - best = candidate; - } - } - return best; - } - - @Override - public boolean insertPattern(ItemStack itemStack) { - return insertPatternWithResult(itemStack) == ECOPatternInsertionResult.INSERTED; - } - - @Override - public ECOPatternInsertionResult insertPatternWithResult(ItemStack itemStack) { + public ECOPatternInsertionResult insertPattern(ItemStack itemStack) { if (itemStack.isEmpty() || buses.isEmpty()) { return ECOPatternInsertionResult.NO_TARGET; } - // 只解码一次,再交给各总线的「已知唯一」快路径,避免每条总线都重扫整个逻辑域。 + // ECO 20.4.x exposes insertion as a result-returning operation on each native bus. ECOPatternInsertionResult last = ECOPatternInsertionResult.NO_TARGET; for (ECOCraftingPatternBusBlockEntity bus : buses) { - ECOPatternInsertionResult result = bus.insertPatternWithResult(itemStack); + ECOPatternInsertionResult result = bus.insertPattern(itemStack); if (result == ECOPatternInsertionResult.INSERTED) { return result; } @@ -121,8 +98,4 @@ public ECOPatternInsertionResult insertPatternWithResult(ItemStack itemStack) { return last; } - @Override - public boolean checksLogicalDomainForDuplicates() { - return false; - } } diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/CompactInterfaceAttachment.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/CompactInterfaceAttachment.java deleted file mode 100644 index 81aea632..00000000 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/CompactInterfaceAttachment.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.sorrowmist.useless.compat.neoecoae.compact.shadow; - -import appeng.api.networking.IManagedGridNode; -import cn.dancingsnow.neoecoae.multiblock.calculator.NEClusterCalculator; -import cn.dancingsnow.neoecoae.multiblock.cluster.NECluster; -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import com.lowdragmc.lowdraglib2.gui.ui.ModularUI; -import com.lowdragmc.lowdraglib2.syncdata.IManaged; -import com.lowdragmc.lowdraglib2.syncdata.rpc.RPCSender; -import com.sorrowmist.useless.compat.neoecoae.compact.block.CompactInterfaceAccess; -import io.netty.buffer.Unpooled; -import net.minecraft.core.HolderLookup; -import net.minecraft.core.RegistryAccess; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.entity.BlockEntity; -import org.jetbrains.annotations.Nullable; - -import java.util.BitSet; - -/** - * 紧凑方块的「通讯接口界面」全部接线,由三个宿主方块实体各持有一个实例。 - * - *

它把 {@link ShadowInterfaceBlockEntity} 的生命周期、菜单分派、LDLib2 包转发与 - * 存档桥接收在一处;宿主只需要覆写几个入口并把调用转过来。

- * - *

包为什么要宿主转发:LDLib2 的两类包都按 {@code (pos, BlockEntityType)} 投递, - * 而世界在宿主坐标上只会给出宿主自己,所以接收端拿到包之后必须再交给影子。

- */ -public final class CompactInterfaceAttachment> { - - /** 影子 @Persisted 状态在宿主存档里的子标签名。 */ - private static final String PERSIST_KEY = "useless_compact_interface"; - - private final BlockEntity host; - private final IManaged hostManaged; - private final NEClusterCalculator.Factory calculator; - private final ShadowNodeLink nodeLink; - - @Nullable - private ShadowInterfaceBlockEntity shadow; - @Nullable - private C joinedCluster; - - public CompactInterfaceAttachment(BlockEntity host, NEClusterCalculator.Factory calculator) { - this.host = host; - this.hostManaged = (IManaged) host; - this.calculator = calculator; - this.nodeLink = new ShadowNodeLink(host); - } - - // ---------------------------------------------------------------- 菜单分派 - - /** - * 伪装分支可用时返回通讯接口界面;否则返回 null,由宿主回退主机面板。 - * - *

分支依据只有 {@code holder.blockState} 这一个两端都一样的值,所以两端 UI 树必然同构。

- */ - @Nullable - public ModularUI createUI(BlockUIMenuType.BlockUIHolder holder) { - if (!CompactInterfaceAccess.requested(holder.blockState)) { - return null; - } - ShadowInterfaceBlockEntity target = shadow(); - return target.supportsInterfaceUi() ? target.createUI(holder) : null; - } - - // ---------------------------------------------------------------- 成形 / 拆除 - - /** 服务端成形后调用:让影子认宿主所在的集群,并把它接上宿主的网格节点。 */ - public void attach(ServerLevel serverLevel, C cluster, IManagedGridNode node) { - ShadowInterfaceBlockEntity target = shadow(); - if (target.compactCluster() != cluster) { - target.updateCluster(cluster); - } - // 集群里那些「按类型找接口」的逻辑(存储集群的 theInterface)只认成员表, - // 而影子是手工造的,得自己进这张表。 - if (joinedCluster != cluster) { - cluster.addBlockEntity(target); - joinedCluster = cluster; - } - nodeLink.attach(target.getMainNode(), serverLevel, node); - } - - /** - * 重新装配前调用:只解除与旧集群的登记。 - * - *

影子对象必须留着——它的 {@code @Persisted} 状态(接口模式、模糊过滤器)在存档恢复时 - * 就灌进去了,而 AE2 的网格节点一旦销毁就不能重建({@code ManagedGridNode} 的初始化数据 - * 已被消费)。重新装配用的还是同一个宿主节点,连接原封不动即可。

- */ - public void release() { - joinedCluster = null; - } - - /** 拆方块 / 卸载区块时调用:连影子节点一起摘掉,避免留下幽灵机器。 */ - public void dispose() { - nodeLink.detach(); - shadow = null; - joinedCluster = null; - } - - /** 宿主每 tick 驱动一次:补建网格节点、把影子的脏字段推给客户端、再跑影子自己的 tick。 */ - public void tick() { - ShadowInterfaceBlockEntity target = shadow; - if (target == null) { - return; - } - nodeLink.tick(); - target.getRootStorage().requireInit(); - if (target.getRootStorage().hasDirtySyncFields()) { - target.sync(false); - } - target.tick(); - } - - // ---------------------------------------------------------------- LDLib2 包转发 - - /** @return true 表示这一包属于影子接口、已由它处理,宿主不要再处理 */ - public boolean forwardRpc(RPCSender sender, byte[] data) { - ShadowInterfaceBlockEntity target = shadow; - if (target == null) { - return false; - } - String method = rpcMethodName(data); - // 包体里只有方法名,所以「谁的方法名」就是唯一的判据。宿主自己的 RPC 只有 L9 的 - // setEcoMegaFilter,与接口界面的五个方法不相交(已按运行时 21.2.0-beta1 逐个核对), - // 因此这里不会把接口界面的交互错投给主机面板。 - if (hasRpc(hostManaged, method) || !hasRpc(target, method)) { - return false; - } - target.handleRPCPacket(sender, data); - return true; - } - - /** @return true 表示这一包是影子发出的字段同步,宿主不要再处理(下标空间不同,错处理会写坏字段) */ - public boolean forwardSync(RegistryAccess access, BitSet changed, byte[] data, CompoundTag extra) { - if (!extra.getBoolean(ShadowInterfaceBlockEntity.SYNC_MARKER)) { - return false; - } - shadow().handleSyncPacket(access, changed, data, extra); - return true; - } - - /** - * Receives ECO's chunked pattern preview on the compact host position. ECO normally looks - * up an {@code ECOMachineInterfaceBlockEntity} in the world, but the compact interface is - * an in-memory shadow and therefore needs this explicit bridge. - */ - public void receivePatternPreview(CompoundTag payload) { - shadow().getPatternPreviewSync().receive(payload); - } - - /** RPC 包体 = {@code varInt(managed 下标) + utf(方法名) + 参数},这里只需要方法名。 */ - private static String rpcMethodName(byte[] data) { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); - try { - buf.readVarInt(); - return buf.readUtf(); - } finally { - buf.release(); - } - } - - private static boolean hasRpc(IManaged owner, String method) { - return owner.getFieldHolder().getRpcMethodMap().containsKey(method); - } - - // ---------------------------------------------------------------- 存档桥接 - - /** 影子的 @Persisted 状态(存储接口模式、63 格模糊过滤器)代存在宿主自己的存档里。 */ - public void save(HolderLookup.Provider provider, CompoundTag tag) { - if (shadow == null) { - return; - } - CompoundTag sub = new CompoundTag(); - shadow.saveManagedPersistentData(provider, sub, false); - tag.put(PERSIST_KEY, sub); - } - - public void load(HolderLookup.Provider provider, CompoundTag tag) { - if (!tag.contains(PERSIST_KEY)) { - return; - } - shadow().loadManagedPersistentData(provider, tag.getCompound(PERSIST_KEY)); - } - - // ---------------------------------------------------------------- 影子对象 - - /** - * 双端都按需建同构的影子对象:服务端在成形时建(之后由 attach 认集群、tick 建节点), - * 客户端在收到第一包或第一次开界面时建。 - */ - private ShadowInterfaceBlockEntity shadow() { - if (shadow == null) { - shadow = new ShadowInterfaceBlockEntity<>(host.getType(), host.getBlockPos(), - host.getBlockState(), calculator); - } - // 存档恢复(loadTag)早于 setLevel,客户端则不存在 attach 过程:两条时序都需要取得真实世界, - // 影子实体才能识别菜单 viewer(isViewer 会比对 level),服务端也才能在正确的维度中 tick。 - if (shadow.getLevel() != host.getLevel()) { - shadow.setLevel(host.getLevel()); - } - return shadow; - } -} diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowComputationHostBlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowComputationHostBlockEntity.java index 98fdcb08..ef455e7b 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowComputationHostBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowComputationHostBlockEntity.java @@ -23,16 +23,6 @@ public ShadowComputationHostBlockEntity(BlockEntityType type, BlockPos pos, B super(type, pos, blockState, ECOTier.L9); } - @Override - public boolean hasNormalNetworkSwitch() { - return false; - } - - @Override - public boolean hasHighEnergyNetworkSwitch() { - return true; - } - @Override public int getSelectedBuildLength() { // 紧凑主机永远是「满长」建造:极限模式判定要求 selected == max。 diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowCraftingHostBlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowCraftingHostBlockEntity.java index d02ea506..eebfcc7f 100644 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowCraftingHostBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowCraftingHostBlockEntity.java @@ -23,16 +23,6 @@ public ShadowCraftingHostBlockEntity(BlockEntityType type, BlockPos pos, Bloc super(type, pos, blockState, ECOTier.L9); } - @Override - public boolean hasNormalNetworkSwitch() { - return false; - } - - @Override - public boolean hasHighEnergyNetworkSwitch() { - return true; - } - @Override public int getSelectedBuildLength() { // 紧凑主机永远是「满长」建造:虚拟合成模式判定要求实际 FX 通道数 == 满长值。 diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowInterfaceBlockEntity.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowInterfaceBlockEntity.java deleted file mode 100644 index 040759d0..00000000 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/shadow/ShadowInterfaceBlockEntity.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.sorrowmist.useless.compat.neoecoae.compact.shadow; - -import appeng.api.orientation.BlockOrientation; -import cn.dancingsnow.neoecoae.blocks.entity.ECOMachineInterfaceBlockEntity; -import cn.dancingsnow.neoecoae.multiblock.calculator.NEClusterCalculator; -import cn.dancingsnow.neoecoae.multiblock.cluster.NECluster; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.HolderLookup; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; - -import java.util.EnumSet; -import java.util.Set; - -/** - * 「影子通讯接口」:不落世界、但复用宿主身份的 ECO 通讯接口方块实体。 - * - *

为什么要复用宿主身份:LDLib2 的 RPC 包与字段同步包都按 - * {@code (BlockPos, BlockEntityType 实例)} 寻址,接收端还要做 - * {@code be.getType() == packet.blockEntityType} 的引用比较。所以这个影子对象必须用 - * 宿主自己的 {@link BlockEntityType} 与宿主坐标构造,包才会先投到宿主, - * 再由宿主 {@code CompactInterfaceAttachment} 转发进来。

- * - *

为什么用 calculator 而不是 cluster 判定界面类型:{@code supportsXxxInterfaceUi()} - * 是「cluster instanceof 或 calculator instanceof」,而 cluster 只存在于服务端, - * calculator 是构造器注入、两端都有 ⇒ 两端界面分派必然一致(这正是第 6 轮崩溃的教训)。

- */ -public class ShadowInterfaceBlockEntity> extends ECOMachineInterfaceBlockEntity { - - /** 影子同步包的身份标记:宿主靠它在自己的同步包里认出「这一包是影子的」。 */ - public static final String SYNC_MARKER = "useless_compact_interface_sync"; - - public ShadowInterfaceBlockEntity(BlockEntityType type, BlockPos pos, BlockState blockState, - NEClusterCalculator.Factory calculator) { - super(type, pos, blockState, calculator); - // 一个 flag 都不要:影子节点额外吃通道时,通道不足会让 AE2 的 isActive() 为假, - // grid.getActiveMachines(...) 就会漏掉它(界面样板列表与模糊过滤全空)。 - getMainNode().setFlags(); - // 影子节点不进世界也不做邻居扫描:它靠宿主节点显式连接入网, - // 以避免每个影子节点加载邻居区块、并将自身登记为占用该区块的网络节点。 - getMainNode().setInWorldNode(false); - } - - /** 供接线层判断缝合是否还有效:{@code NECluster.destroy()} 会把成员的 cluster 置空。 */ - public C compactCluster() { - return cluster; - } - - @Override - public synchronized void writeCustomSyncData(HolderLookup.Provider provider, CompoundTag tag) { - super.writeCustomSyncData(provider, tag); - tag.putBoolean(SYNC_MARKER, true); - } - - // ------------------------------------------------ 影子卫生:绝不回写世界 - // 这些入口在 ECO 里都会按 worldPosition 改宿主坐标上的真实方块,影子必须掐掉。 - - @Override - public void updateState(boolean updateExposed) { - } - - @Override - public void markForUpdate() { - } - - @Override - public void saveChanges() { - } - - @Override - public Set getGridConnectableSides(BlockOrientation orientation) { - return EnumSet.noneOf(Direction.class); - } -} diff --git a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/ui/CompactSingleBlockUI.java b/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/ui/CompactSingleBlockUI.java deleted file mode 100644 index 1dd864ca..00000000 --- a/src/main/java/com/sorrowmist/useless/compat/neoecoae/compact/ui/CompactSingleBlockUI.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.sorrowmist.useless.compat.neoecoae.compact.ui; - -import com.lowdragmc.lowdraglib2.gui.ui.ModularUI; -import com.lowdragmc.lowdraglib2.gui.ui.UI; -import com.lowdragmc.lowdraglib2.gui.ui.UIElement; -import com.lowdragmc.lowdraglib2.gui.ui.data.TextWrap; -import com.lowdragmc.lowdraglib2.gui.ui.elements.TextElement; -import com.lowdragmc.lowdraglib2.gui.ui.elements.inventory.InventorySlots; -import com.lowdragmc.lowdraglib2.gui.ui.style.StylesheetManager; -import cn.dancingsnow.neoecoae.gui.theme.NEStyleSheets; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Player; - -import java.util.List; - -/** - * Small single-block overview used by compact hosts. - * - *

The ECO controller screens contain a multiblock builder and a structure preview. Those - * controls are meaningless for a compact block, so the overview deliberately exposes only - * the fixed hardware, pooled capacity, and the player inventory. Detailed management remains - * on the sneaking interface entry and on the dedicated fluid/pattern item interactions.

- */ -public final class CompactSingleBlockUI { - private CompactSingleBlockUI() {} - - public static ModularUI create(Player player, Component title, Component... lines) { - UIElement root = new UIElement().addClass("panel_bg"); - root.addChild(new TextElement().setText(title).textStyle(style -> style - .adaptiveWidth(true).adaptiveHeight(true).textWrap(TextWrap.NONE) - .textColor(0x3F3D52).textShadow(false))); - for (Component line : lines) { - root.addChild(new TextElement().setText(line).textStyle(style -> style - .adaptiveWidth(true).adaptiveHeight(true).textWrap(TextWrap.HOVER_ROLL) - .textColor(0x555266).textShadow(false))); - } - InventorySlots inventory = new InventorySlots(); - root.addChild(inventory); - return new ModularUI(UI.of(root, - List.of(StylesheetManager.INSTANCE.getStylesheetSafe(NEStyleSheets.ECO))), player); - } -} diff --git a/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorBlockEntity.java b/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorBlockEntity.java index a9c90219..e4cc864d 100644 --- a/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorBlockEntity.java +++ b/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorBlockEntity.java @@ -19,10 +19,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.HolderLookup; -import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; import net.minecraft.network.chat.Component; import net.minecraft.world.Containers; import net.minecraft.world.MenuProvider; @@ -31,7 +28,6 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerData; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -50,15 +46,6 @@ public final class OreGeneratorBlockEntity extends BlockEntity public static final long DEFAULT_OUTPUT_RATE = 1L; public static final int MENU_DATA_COUNT = 7; - private static final TagKey RAW_MATERIALS = TagKey.create( - Registries.ITEM, - new ResourceLocation("c", "raw_materials") - ); - private static final TagKey ORES = TagKey.create( - Registries.ITEM, - new ResourceLocation("c", "ores") - ); - private final OreGeneratorSampleHandler samples = new OreGeneratorSampleHandler( ConfigManager::getOreGeneratorSlots, OreGeneratorBlockEntity::isValidSample, @@ -238,7 +225,7 @@ private void tryExportItem(ItemStack stack) { } public static boolean isValidSample(ItemStack stack) { - return !stack.isEmpty() && (stack.is(RAW_MATERIALS) || stack.is(ORES)); + return OreGeneratorSampleHandler.isValidSample(stack); } private void samplesChanged() { diff --git a/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleHandler.java b/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleHandler.java index a4b79770..7c229f8d 100644 --- a/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleHandler.java +++ b/src/main/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleHandler.java @@ -1,6 +1,11 @@ package com.sorrowmist.useless.content.blockentities; import net.minecraft.world.item.ItemStack; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.Tags; import java.util.function.IntSupplier; import java.util.function.Predicate; @@ -9,6 +14,15 @@ public final class OreGeneratorSampleHandler extends RecoverableItemStackHandler { public static final int MAX_SLOTS = 540; + // Keep common-tag datapacks working alongside Forge 1.20.1's native tags. + private static final TagKey COMMON_ORES = ItemTags.create(new ResourceLocation("c", "ores")); + private static final TagKey COMMON_RAW_MATERIALS = ItemTags.create(new ResourceLocation("c", "raw_materials")); + + public static boolean isValidSample(ItemStack stack) { + return !stack.isEmpty() && (stack.is(Tags.Items.ORES) || stack.is(Tags.Items.RAW_MATERIALS) + || stack.is(COMMON_ORES) || stack.is(COMMON_RAW_MATERIALS)); + } + public OreGeneratorSampleHandler(IntSupplier activeSlots, Predicate validator, Runnable changeListener) { super(MAX_SLOTS, 0, activeSlots, validator, changeListener); diff --git a/src/main/java/com/sorrowmist/useless/content/menus/OreGeneratorMenu.java b/src/main/java/com/sorrowmist/useless/content/menus/OreGeneratorMenu.java index c744a67b..b5169b1a 100644 --- a/src/main/java/com/sorrowmist/useless/content/menus/OreGeneratorMenu.java +++ b/src/main/java/com/sorrowmist/useless/content/menus/OreGeneratorMenu.java @@ -38,7 +38,7 @@ private static RecoverableItemStackHandler handler(Inventory inventory, BlockPos return generator.getSamples(); } return new OreGeneratorSampleHandler(ConfigManager::getOreGeneratorSlots, - stack -> false, () -> { }); + OreGeneratorSampleHandler::isValidSample, () -> { }); } public BlockPos getBlockPos() { diff --git a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java index 203163c9..5fc41e9d 100644 --- a/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java +++ b/src/main/java/com/sorrowmist/useless/content/recipe/adapters/RecipeAdapterCompatRegistry.java @@ -433,7 +433,10 @@ private static void registerAvaritia() { } private static void registerNeoECOAE() { - registerByName("com.sorrowmist.useless.content.recipe.adapters.eco.IntegratedWorkingStationRecipeAdapter"); + String adapterClass = "com.sorrowmist.useless.content.recipe.adapters.eco.IntegratedWorkingStationRecipeAdapter"; + if (isClassAvailable(adapterClass)) { + registerByName(adapterClass); + } } private static void registerNaturesAura() { diff --git a/src/main/java/com/sorrowmist/useless/mixin/compact/MenuDataCodecsCompactMixin.java b/src/main/java/com/sorrowmist/useless/mixin/compact/MenuDataCodecsCompactMixin.java deleted file mode 100644 index b1605a00..00000000 --- a/src/main/java/com/sorrowmist/useless/mixin/compact/MenuDataCodecsCompactMixin.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.sorrowmist.useless.mixin.compact; - -import com.lowdragmc.lowdraglib2.gui.factory.BlockUIMenuType; -import com.lowdragmc.lowdraglib2.gui.holder.ModularUIContainerMenu; -import com.sorrowmist.useless.compat.neoecoae.compact.entity.CompactF9BlockEntity; -import cn.dancingsnow.neoecoae.gui.crafting.PatternPreviewCodec; -import cn.dancingsnow.neoecoae.network.MenuDataTransport; -import net.minecraft.core.BlockPos; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.entity.player.Player; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** Routes ECO PATTERNS chunks to the in-memory compact interface shadow. */ -@Mixin(targets = "cn.dancingsnow.neoecoae.network.MenuDataCodecs", remap = false) -public final class MenuDataCodecsCompactMixin { - @Inject(method = "receive", at = @At("HEAD"), cancellable = true, remap = false) - private static void uselessMod$receiveCompactPreview( - Player player, MenuDataTransport.Channel channel, FriendlyByteBuf buf, CallbackInfo ci) { - if (channel != MenuDataTransport.Channel.PATTERNS - || !(player.containerMenu instanceof ModularUIContainerMenu menu) - || !(menu.uiHolder instanceof BlockUIMenuType.BlockUIHolder holder) - || !(player.level().getBlockEntity(holder.pos) instanceof CompactF9BlockEntity compact)) { - return; - } - BlockPos target = buf.readBlockPos(); - if (!target.equals(holder.pos)) { - throw new IllegalArgumentException("Compact pattern preview target does not match menu host"); - } - CompoundTag payload = PatternPreviewCodec.read(buf); - compact.receivePatternPreview(payload); - ci.cancel(); - } -} diff --git a/src/main/resources/META-INF/coremods.json b/src/main/resources/META-INF/coremods.json new file mode 100644 index 00000000..50ec10c2 --- /dev/null +++ b/src/main/resources/META-INF/coremods.json @@ -0,0 +1,3 @@ +{ + "useless_mod_neoecoae_crafting_compat": "coremods/neoecoae_crafting_service_compat.js" +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 3b70b202..64df9f3d 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -12,6 +12,21 @@ mandatory=true versionRange="[47.3.10,48)" ordering="NONE" side="BOTH" + +[[dependencies.useless_mod]] +modId="neoecoae" +mandatory=false +versionRange="[20.4.2,20.5)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ldlib" +mandatory=false +versionRange="[1.0.52.a,1.0.53)" +ordering="AFTER" +side="BOTH" + [[dependencies.useless_mod]] modId="minecraft" mandatory=true @@ -24,3 +39,272 @@ mandatory=true versionRange="[15.4.8,16)" ordering="AFTER" side="BOTH" + +# Optional integrations: absence is allowed; installed versions must match the supported API line. +# Minimums are the versions used by this Forge build, taken from each dependency's mods.toml. + +[[dependencies.useless_mod]] +modId="advanced_ae" +mandatory=false +versionRange="[1.2.7-1.20.1,2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="apotheosis" +mandatory=false +versionRange="[7.4.8,8)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="appmek" +mandatory=false +versionRange="[1.4.2,2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="appflux" +mandatory=false +versionRange="[1.20-1.3.2-forge,1.21)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="architectury" +mandatory=false +versionRange="[9.2.14,10)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="bookshelf" +mandatory=false +versionRange="[20.2.13,21)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="botanypots" +mandatory=false +versionRange="[13.0.41,14)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="brandonscore" +mandatory=false +versionRange="[3.2.1.302,4)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="create" +mandatory=false +versionRange="[6.0.6,6.1)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="curios" +mandatory=false +versionRange="[5.14.1+1.20.1,6)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="draconicevolution" +mandatory=false +versionRange="[3.1.2.621,4)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="enderio" +mandatory=false +versionRange="[6.2.13-beta,7)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="exdeorum" +mandatory=false +versionRange="[1.46,2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="expatternprovider" +mandatory=false +versionRange="[1.20-1.4.6-forge,1.21)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="extendedcrafting" +mandatory=false +versionRange="[6.0.10,7)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="fluxnetworks" +mandatory=false +versionRange="[7.2.1.15,8)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ftbchunks" +mandatory=false +versionRange="[2001.3.6,2002)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ftbteams" +mandatory=false +versionRange="[2001.3.1,2002)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ftbultimine" +mandatory=false +versionRange="[2001.1.7,2002)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="geckolib" +mandatory=false +versionRange="[4.8.2,5)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="hostilenetworks" +mandatory=false +versionRange="[5.3.3,6)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="industrialforegoing" +mandatory=false +versionRange="[3.5.19,4)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="jade" +mandatory=false +versionRange="[11.13.2+forge,12)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="emi" +mandatory=false +versionRange="[1.1.22+1.20.1+forge,2)" +ordering="AFTER" +side="CLIENT" + +[[dependencies.useless_mod]] +modId="jei" +mandatory=false +versionRange="[15.20.0.116,16)" +ordering="AFTER" +side="CLIENT" + +[[dependencies.useless_mod]] +modId="kubejs" +mandatory=false +versionRange="[2001.6.5-build.16,2002)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="mekanism" +mandatory=false +versionRange="[10.4.15,10.5)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="mekanismgenerators" +mandatory=false +versionRange="[10.4.15,10.5)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="placebo" +mandatory=false +versionRange="[8.6.3,9)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="powah" +mandatory=false +versionRange="[5.0.11,6)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="rhino" +mandatory=false +versionRange="[2001.2.3-build.10,2002)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="summoningrituals" +mandatory=false +versionRange="[1.20.1-2.0.8,1.20.2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="titanium" +mandatory=false +versionRange="[3.8.32,4)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="cucumber" +mandatory=false +versionRange="[7.0.15,8)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="glodium" +mandatory=false +versionRange="[1.20-1.5-forge,1.21)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="mekanism_extras" +mandatory=false +versionRange="[1.20.1-1.4.1,1.20.2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="ponder" +mandatory=false +versionRange="[1.0.80,2)" +ordering="AFTER" +side="BOTH" + +[[dependencies.useless_mod]] +modId="flywheel" +mandatory=false +versionRange="[1.0.4,2)" +ordering="AFTER" +side="BOTH" diff --git a/src/main/resources/coremods/neoecoae_crafting_service_compat.js b/src/main/resources/coremods/neoecoae_crafting_service_compat.js new file mode 100644 index 00000000..78f3a68b --- /dev/null +++ b/src/main/resources/coremods/neoecoae_crafting_service_compat.js @@ -0,0 +1,105 @@ +var VarInsnNode = Java.type("org.objectweb.asm.tree.VarInsnNode"); + +var handlerName = "neoecoae$submitJobWithSimulationFlag"; +var oldHandlerDescriptor = "(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;Z" + + "Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;)V"; +var newHandlerDescriptor = "(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;" + + "Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;)V"; +var oldTargetDescriptor = "submitJob(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;Z)" + + "Lappeng/api/networking/crafting/ICraftingSubmitResult;"; +var newTargetDescriptor = "submitJob(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;)" + + "Lappeng/api/networking/crafting/ICraftingSubmitResult;"; +var oldGenericSignature = "(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;Z" + + "Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable<" + + "Lappeng/api/networking/crafting/ICraftingSubmitResult;>;)V"; +var newGenericSignature = "(Lappeng/api/networking/crafting/ICraftingPlan;" + + "Lappeng/api/networking/crafting/ICraftingRequester;" + + "Lappeng/api/networking/crafting/ICraftingCPU;Z" + + "Lappeng/api/networking/security/IActionSource;" + + "Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable<" + + "Lappeng/api/networking/crafting/ICraftingSubmitResult;>;)V"; + +function initializeCoreMod() { + return { + "neoecoae_crafting_service_compat": { + "target": { + "type": "CLASS", + "name": "cn.dancingsnow.neoecoae.mixins.CraftingServiceMixin" + }, + "transformer": function(classNode) { + for (var methodIndex = 0; methodIndex < classNode.methods.size(); methodIndex++) { + var method = classNode.methods.get(methodIndex); + if (method.name !== handlerName || method.desc !== oldHandlerDescriptor) { + continue; + } + + method.desc = newHandlerDescriptor; + if (method.signature === oldGenericSignature) { + method.signature = newGenericSignature; + } + + var instructions = method.instructions.toArray(); + for (var instructionIndex = 0; instructionIndex < instructions.length; instructionIndex++) { + var instruction = instructions[instructionIndex]; + if (instruction instanceof VarInsnNode && instruction.var >= 7) { + instruction.var--; + } + } + + if (method.localVariables !== null) { + for (var localIndex = method.localVariables.size() - 1; localIndex >= 0; localIndex--) { + var local = method.localVariables.get(localIndex); + if (local.index === 6 && local.name === "simulate") { + method.localVariables.remove(localIndex); + } else if (local.index >= 7) { + local.index--; + } + } + } + + if (method.parameters !== null && method.parameters.size() === 7) { + method.parameters.remove(5); + } + method.maxLocals--; + + if (method.visibleAnnotations !== null) { + for (var annotationIndex = 0; annotationIndex < method.visibleAnnotations.size(); annotationIndex++) { + var annotation = method.visibleAnnotations.get(annotationIndex); + if (annotation.desc !== "Lorg/spongepowered/asm/mixin/injection/Inject;") { + continue; + } + for (var valueIndex = 0; valueIndex < annotation.values.size(); valueIndex += 2) { + if (annotation.values.get(valueIndex) === "method") { + var targets = annotation.values.get(valueIndex + 1); + for (var targetIndex = 0; targetIndex < targets.size(); targetIndex++) { + if (targets.get(targetIndex) === oldTargetDescriptor) { + targets.set(targetIndex, newTargetDescriptor); + } + } + } + } + } + } + + break; + } + return classNode; + } + } + }; +} diff --git a/src/main/resources/useless_mod.compact.mixins.json b/src/main/resources/useless_mod.compact.mixins.json index 77159ef8..c40771f4 100644 --- a/src/main/resources/useless_mod.compact.mixins.json +++ b/src/main/resources/useless_mod.compact.mixins.json @@ -3,8 +3,7 @@ "package": "com.sorrowmist.useless.mixin.compact", "compatibilityLevel": "JAVA_17", "mixins": [ - "GridMachineClassMixin", - "MenuDataCodecsCompactMixin" + "GridMachineClassMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java new file mode 100644 index 00000000..ddf88f23 --- /dev/null +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/DependencyMetadataTest.java @@ -0,0 +1,87 @@ +package com.sorrowmist.useless.compat.forge1201; + +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.toml.TomlParser; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.Manifest; +import java.util.zip.ZipFile; + +import static org.junit.jupiter.api.Assertions.*; + +class DependencyMetadataTest { + private static Map requirements() throws Exception { + Config metadata = new TomlParser().parse(Files.readString(Path.of("src/main/resources/META-INF/mods.toml"))); + List dependencies = metadata.get("dependencies.useless_mod"); + Map result = new HashMap<>(); + for (Config dependency : dependencies) { + String id = dependency.get("modId"); + assertNull(result.put(id, dependency), "Duplicate dependency: " + id); + assertTrue(Set.of("BOTH", "CLIENT", "SERVER").contains(dependency.get("side")), id); + } + return result; + } + + @Test void everyCompiledForgeModHasABoundedCompatibleRuntimeRequirement() throws Exception { + var requirements = requirements(); + Set checked = new HashSet<>(); + for (String entry : System.getProperty("uselessMod.compileClasspath").split(File.pathSeparator)) { + if (!entry.endsWith(".jar")) continue; + try (var jar = new ZipFile(entry)) { + var metadataEntry = jar.getEntry("META-INF/mods.toml"); + if (metadataEntry == null) continue; + Config metadata = new TomlParser().parse(new String( + jar.getInputStream(metadataEntry).readAllBytes(), StandardCharsets.UTF_8)); + List mods = metadata.get("mods"); + if (mods == null) continue; + for (Config mod : mods) { + String id = mod.get("modId"); + if (id.equals("forge") || id.equals("minecraft")) continue; + Config requirement = requirements.get(id); + assertNotNull(requirement, "Missing runtime version requirement: " + id); + String version = mod.get("version"); + if (version.equals("${file.jarVersion}")) { + version = new Manifest(jar.getInputStream(jar.getEntry("META-INF/MANIFEST.MF"))) + .getMainAttributes().getValue("Implementation-Version"); + } + String spec = requirement.get("versionRange"); + var range = VersionRange.createFromVersionSpec(spec); + assertTrue(range.containsVersion(new DefaultArtifactVersion(version)), id + " " + version + " outside " + spec); + assertTrue(range.getRestrictions().stream().allMatch(r -> r.getLowerBound() != null + && r.getUpperBound() != null), "Unbounded API line: " + id); + assertEquals(id.equals("ae2"), requirement.get("mandatory"), id); + checked.add(id); + } + } + } + assertEquals(41, checked.size(), "All compiled mod metadata must be inspected"); + } + + @Test void obsoleteApisAreRejectedButTheUpdatedPackVersionsRemainAllowed() throws Exception { + var requirements = requirements(); + for (var entry : Map.of("ae2", "15.4.10", "expatternprovider", "1.20-1.4.12-forge", + "jei", "15.48.0.177", "extendedcrafting", "6.1.5u", "mekanism", "10.4.15").entrySet()) { + var range = VersionRange.createFromVersionSpec(requirements.get(entry.getKey()).get("versionRange")); + assertTrue(range.containsVersion(new DefaultArtifactVersion(entry.getValue())), entry.toString()); + } + for (var entry : Map.of("ae2", "14.0.0", "create", "0.5.1", "mekanism", "10.3.0", + "jei", "15.0.0", "exdeorum", "1.0").entrySet()) { + var range = VersionRange.createFromVersionSpec(requirements.get(entry.getKey()).get("versionRange")); + assertFalse(range.containsVersion(new DefaultArtifactVersion(entry.getValue())), entry.toString()); + } + assertTrue(requirements.get("forge").get("mandatory")); + assertTrue(requirements.get("minecraft").get("mandatory")); + assertFalse(requirements.get("create").get("mandatory")); + } +} diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegrationPortTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegrationPortTest.java new file mode 100644 index 00000000..22ec70b9 --- /dev/null +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/OptionalIntegrationPortTest.java @@ -0,0 +1,15 @@ +package com.sorrowmist.useless.compat.forge1201; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OptionalIntegrationPortTest { + @Test void integrationClassesAreCheckedWithoutLoadingOptionalApiTypes() { + assertTrue(OptionalIntegration.hasClass(OptionalIntegrationPortTest.class.getName())); + assertFalse(OptionalIntegration.hasClass( + "com.sorrowmist.useless.compat.neoecoae.compact.NeoEcoCompactRegistry")); + assertFalse(OptionalIntegration.hasClass("cn.dancingsnow.neoecoae.api.EcoOptionalType")); + } +} diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeCapabilityTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeCapabilityTest.java index 5e0ebabb..ecc14c4d 100644 --- a/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeCapabilityTest.java +++ b/src/portBridgeTest/java/com/sorrowmist/useless/compat/forge1201/capability/NativeCapabilityTest.java @@ -6,7 +6,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraftforge.common.capabilities.NativeTestCapabilities; +import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.energy.EnergyStorage; import net.minecraftforge.energy.IEnergyStorage; @@ -19,6 +19,10 @@ class NativeCapabilityTest { @BeforeAll static void bootstrap() { RecipeTestBootstrap.initialize(); } + // Forge capability constants require launcher transformers, unavailable in this headless test source set. + @SuppressWarnings("unchecked") private static Capability capability() { + return (Capability) mock(Capability.class); + } @SuppressWarnings("unchecked") private static BlockEntityType type() { return (BlockEntityType) mock(BlockEntityType.class); } @@ -26,72 +30,79 @@ private static BlockEntity entity(BlockEntityType type) { BlockEntity entity=mock(BlockEntity.class); doReturn(type).when(entity).getType(); return entity; } - @Test void realNativeCapabilityTokenPreservesSidedAndUnsidedAccess() { + @Test void capabilityTokenPreservesSidedAndUnsidedAccess() { var registry=new BlockCapabilityRegistry(); var type=type(); var entity=entity(type); + Capability energyCapability=capability(); + Capability otherCapability=capability(); var energy=new EnergyStorage(10000); - registry.register(NativeTestCapabilities.ENERGY,type,(be,side)->side==Direction.UP||side==null?energy:null); + registry.register(energyCapability,type,(be,side)->side==Direction.UP||side==null?energy:null); var provider=registry.provider(entity); assertNotNull(provider); - var up=provider.getCapability(NativeTestCapabilities.ENERGY,Direction.UP); + var up=provider.getCapability(energyCapability,Direction.UP); assertSame(energy,up.orElseThrow(IllegalStateException::new)); - assertSame(up,provider.getCapability(NativeTestCapabilities.ENERGY,Direction.UP)); - assertSame(energy,provider.getCapability(NativeTestCapabilities.ENERGY,null).orElseThrow(IllegalStateException::new)); - assertFalse(provider.getCapability(NativeTestCapabilities.ENERGY,Direction.DOWN).isPresent()); - assertFalse(provider.getCapability(NativeTestCapabilities.ITEM_HANDLER,null).isPresent()); + assertSame(up,provider.getCapability(energyCapability,Direction.UP)); + assertSame(energy,provider.getCapability(energyCapability,null).orElseThrow(IllegalStateException::new)); + assertFalse(provider.getCapability(energyCapability,Direction.DOWN).isPresent()); + assertFalse(provider.getCapability(otherCapability,null).isPresent()); assertEquals(1200,up.orElseThrow(IllegalStateException::new).receiveEnergy(1200,false)); assertEquals(1200,energy.getEnergyStored()); } @Test void withdrawingAndReenablingAConfigurableSideInvalidatesOldView() { var registry=new BlockCapabilityRegistry(); var type=type(); var entity=entity(type); + Capability energyCapability=capability(); var active=new AtomicReference(new EnergyStorage(1000)); - registry.register(NativeTestCapabilities.ENERGY,type,(be,side)->active.get()); - var provider=registry.provider(entity); var old=provider.getCapability(NativeTestCapabilities.ENERGY,Direction.NORTH); + registry.register(energyCapability,type,(be,side)->active.get()); + var provider=registry.provider(entity); var old=provider.getCapability(energyCapability,Direction.NORTH); var invalidated=new AtomicBoolean(); old.addListener(ignored->invalidated.set(true)); active.set(null); - assertFalse(provider.getCapability(NativeTestCapabilities.ENERGY,Direction.NORTH).isPresent()); + assertFalse(provider.getCapability(energyCapability,Direction.NORTH).isPresent()); assertTrue(invalidated.get()); assertFalse(old.isPresent()); var next=new EnergyStorage(2000); active.set(next); - assertSame(next,provider.getCapability(NativeTestCapabilities.ENERGY,Direction.NORTH).orElseThrow(IllegalStateException::new)); + assertSame(next,provider.getCapability(energyCapability,Direction.NORTH).orElseThrow(IllegalStateException::new)); } @Test void unloadInvalidatesAllResolvedOptionsAndRemovalPreventsTransfers() { var registry=new BlockCapabilityRegistry(); var type=type(); var entity=entity(type); - registry.register(NativeTestCapabilities.ENERGY,type,(be,side)->new EnergyStorage(1000)); + Capability energyCapability=capability(); + registry.register(energyCapability,type,(be,side)->new EnergyStorage(1000)); var provider=registry.provider(entity); - var up=provider.getCapability(NativeTestCapabilities.ENERGY,Direction.UP); - var down=provider.getCapability(NativeTestCapabilities.ENERGY,Direction.DOWN); + var up=provider.getCapability(energyCapability,Direction.UP); + var down=provider.getCapability(energyCapability,Direction.DOWN); assertTrue(up.isPresent()); assertTrue(down.isPresent()); provider.invalidate(); assertFalse(up.isPresent()); assertFalse(down.isPresent()); when(entity.isRemoved()).thenReturn(true); - assertFalse(provider.getCapability(NativeTestCapabilities.ENERGY,null).isPresent()); + assertFalse(provider.getCapability(energyCapability,null).isPresent()); } @Test void queryDoesNotLoadChunksAndPreservesRequestedSide() { + Capability energyCapability=capability(); var level=mock(Level.class); var entity=mock(BlockEntity.class); var pos=new BlockPos(42,64,7); - assertNull(CapabilityAccess.get(level,NativeTestCapabilities.ENERGY,pos,Direction.WEST)); + assertNull(CapabilityAccess.get(level,energyCapability,pos,Direction.WEST)); verify(level,never()).getBlockEntity(any()); when(level.isLoaded(pos)).thenReturn(true); when(level.getBlockEntity(pos)).thenReturn(entity); var energy=new EnergyStorage(1000); - when(entity.getCapability(NativeTestCapabilities.ENERGY,Direction.WEST)).thenReturn(LazyOptional.of(()->energy)); - assertSame(energy,CapabilityAccess.get(level,NativeTestCapabilities.ENERGY,pos,Direction.WEST)); + when(entity.getCapability(energyCapability,Direction.WEST)).thenReturn(LazyOptional.of(()->energy)); + assertSame(energy,CapabilityAccess.get(level,energyCapability,pos,Direction.WEST)); when(entity.isRemoved()).thenReturn(true); - assertNull(CapabilityAccess.get(level,NativeTestCapabilities.ENERGY,pos,Direction.WEST)); + assertNull(CapabilityAccess.get(level,energyCapability,pos,Direction.WEST)); } @Test void staleBlockEntityFromBeforeReplacementIsNeverUsed() { + Capability energyCapability=capability(); var level=mock(Level.class); var pos=BlockPos.ZERO; when(level.isLoaded(pos)).thenReturn(true); var stale=mock(BlockEntity.class); var current=mock(BlockEntity.class); when(level.getBlockEntity(pos)).thenReturn(current); var energy=new EnergyStorage(1000); - when(current.getCapability(NativeTestCapabilities.ENERGY,null)).thenReturn(LazyOptional.of(()->energy)); - assertSame(energy,CapabilityAccess.get(level,NativeTestCapabilities.ENERGY,pos,null,stale,null)); + when(current.getCapability(energyCapability,null)).thenReturn(LazyOptional.of(()->energy)); + assertSame(energy,CapabilityAccess.get(level,energyCapability,pos,null,stale,null)); verify(stale,never()).getCapability(any(),any()); } @Test void duplicateRegistrationsFailRatherThanSilentlyChoosingAnotherHandler() { var registry=new BlockCapabilityRegistry(); var type=type(); - registry.register(NativeTestCapabilities.ENERGY,type,(be,side)->new EnergyStorage(1)); - assertThrows(IllegalStateException.class,()->registry.register(NativeTestCapabilities.ENERGY,type,(be,side)->new EnergyStorage(2))); + Capability energyCapability=capability(); + registry.register(energyCapability,type,(be,side)->new EnergyStorage(1)); + assertThrows(IllegalStateException.class,()->registry.register(energyCapability,type,(be,side)->new EnergyStorage(2))); assertNull(registry.provider(entity(type()))); } } diff --git a/src/portBridgeTest/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleTest.java b/src/portBridgeTest/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleTest.java new file mode 100644 index 00000000..b21a86e9 --- /dev/null +++ b/src/portBridgeTest/java/com/sorrowmist/useless/content/blockentities/OreGeneratorSampleTest.java @@ -0,0 +1,108 @@ +package com.sorrowmist.useless.content.blockentities; + +import com.sorrowmist.useless.compat.forge1201.RecipeTestBootstrap; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraftforge.common.Tags; +import net.minecraftforge.items.SlotItemHandler; +import org.junit.jupiter.api.*; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class OreGeneratorSampleTest { + private Map, List>> previousTags; + + @BeforeAll static void bootstrap() { RecipeTestBootstrap.initialize(); } + + @BeforeEach void bindNativeAndCommonTags() { + previousTags = BuiltInRegistries.ITEM.getTags().collect(Collectors.toMap( + pair -> pair.getFirst(), pair -> pair.getSecond().stream().toList())); + Map, List>> tags = new HashMap<>(previousTags); + tags.put(Tags.Items.ORES, List.of(Items.IRON_ORE.builtInRegistryHolder(), + Items.DEEPSLATE_IRON_ORE.builtInRegistryHolder())); + tags.put(Tags.Items.RAW_MATERIALS, List.of(Items.RAW_IRON.builtInRegistryHolder())); + tags.put(ItemTags.create(new ResourceLocation("c", "ores")), + List.of(Items.GOLD_ORE.builtInRegistryHolder())); + tags.put(ItemTags.create(new ResourceLocation("c", "raw_materials")), + List.of(Items.RAW_GOLD.builtInRegistryHolder())); + BuiltInRegistries.ITEM.bindTags(tags); + } + + @AfterEach void restoreTags() { + BuiltInRegistries.ITEM.resetTags(); + BuiltInRegistries.ITEM.bindTags(previousTags); + } + + @Test void forgeOnlyOresAndRawMaterialsAreAccepted() { + for (Item item : List.of(Items.IRON_ORE, Items.DEEPSLATE_IRON_ORE, Items.RAW_IRON)) { + assertTrue(OreGeneratorSampleHandler.isValidSample(new ItemStack(item)), item.toString()); + } + } + + @Test void commonDatapackTagsRemainSupportedWithoutForgeMembership() { + assertTrue(OreGeneratorSampleHandler.isValidSample(new ItemStack(Items.GOLD_ORE))); + assertTrue(OreGeneratorSampleHandler.isValidSample(new ItemStack(Items.RAW_GOLD))); + } + + @Test void emptyStacksIngotsAndStorageBlocksAreRejected() { + for (ItemStack stack : List.of(ItemStack.EMPTY, new ItemStack(Items.IRON_INGOT), + new ItemStack(Items.RAW_IRON_BLOCK), new ItemStack(Items.COBBLESTONE))) { + assertFalse(OreGeneratorSampleHandler.isValidSample(stack)); + } + } + + @Test void nativeMenuSlotAcceptsOneSampleAndLeavesTheRemainingItemsWithThePlayer() { + AtomicInteger changes = new AtomicInteger(); + var handler = new OreGeneratorSampleHandler(() -> 9, + OreGeneratorSampleHandler::isValidSample, changes::incrementAndGet); + var slot = new SlotItemHandler(handler, 0, 0, 0); + ItemStack carried = new ItemStack(Items.IRON_ORE, 64); + assertTrue(slot.mayPlace(carried)); + assertEquals(63, handler.insertItem(0, carried, true).getCount()); + assertTrue(handler.getStackInSlot(0).isEmpty()); + assertEquals(0, changes.get()); + ItemStack remainder = handler.insertItem(0, carried, false); + assertEquals(63, remainder.getCount()); + assertEquals(64, carried.getCount()); + assertEquals(1, handler.getStackInSlot(0).getCount()); + assertEquals(1, changes.get()); + assertEquals(63, handler.insertItem(0, remainder, false).getCount()); + } + + @Test void sampleSurvivesSaveLoadAndConfigurationReductionAllowsRecoveryOnly() { + AtomicInteger slots = new AtomicInteger(10); + var handler = new OreGeneratorSampleHandler(slots::get, + OreGeneratorSampleHandler::isValidSample, () -> {}); + handler.setStackInSlot(9, new ItemStack(Items.RAW_IRON, 32)); + assertEquals(1, handler.getStackInSlot(9).getCount()); + var saved = handler.serializeNBT(); + slots.set(9); + var loaded = new OreGeneratorSampleHandler(slots::get, + OreGeneratorSampleHandler::isValidSample, () -> {}); + loaded.deserializeNBT(saved); + assertTrue(loaded.getStackInSlot(9).is(Items.RAW_IRON)); + assertFalse(new SlotItemHandler(loaded, 9, 0, 0).mayPlace(new ItemStack(Items.IRON_ORE))); + assertTrue(loaded.extractItem(9, 1, false).is(Items.RAW_IRON)); + assertTrue(loaded.getStackInSlot(9).isEmpty()); + } + + @Test void validationTracksDatapackTagReloadInsteadOfCachingItemMembership() { + ItemStack sample = new ItemStack(Items.IRON_ORE); + assertTrue(OreGeneratorSampleHandler.isValidSample(sample)); + BuiltInRegistries.ITEM.resetTags(); + BuiltInRegistries.ITEM.bindTags(previousTags); + assertFalse(OreGeneratorSampleHandler.isValidSample(sample)); + } +} diff --git a/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntityTest.java b/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntityTest.java index 5828c7b0..d4addabb 100644 --- a/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntityTest.java +++ b/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/exposed/ExposedPatternBusBlockEntityTest.java @@ -1,21 +1,17 @@ package com.sorrowmist.useless.compat.neoecoae.compact.exposed; -import appeng.api.networking.GridHelper; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; import appeng.api.networking.IManagedGridNode; -import appeng.api.networking.crafting.ICraftingProvider; -import cn.dancingsnow.neoecoae.config.NEConfig; -import cn.dancingsnow.neoecoae.blocks.entity.ECOMachineInterfaceBlockEntity; -import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; +import appeng.api.networking.crafting.ICraftingService; import com.sorrowmist.useless.InventoryTestBootstrap; +import com.sorrowmist.useless.compat.neoecoae.compact.provider.CombinedPatternInventory; import com.sorrowmist.useless.compat.neoecoae.compact.cluster.CompactCraftingCluster; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.entity.BlockEntityType; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -46,46 +42,37 @@ void domainMembershipKeepsDispatchOnTheHost() throws ReflectiveOperationExceptio } @Test - void shiftingChildOffsetsInvalidatesTheWholeInterfaceEvenAtTheSameTotalSize() { - var node = mock(IManagedGridNode.class, RETURNS_SELF); + void advertisedPatternChangesRequestAHostProviderRefresh() { + var node = mock(IManagedGridNode.class); var gridNode = mock(IGridNode.class); var grid = mock(IGrid.class); - when(node.getNode()).thenReturn(gridNode); + var service = mock(ICraftingService.class); when(node.getGrid()).thenReturn(grid); + when(node.getNode()).thenReturn(gridNode); when(gridNode.getGrid()).thenReturn(grid); - var machineInterface = mock(ECOMachineInterfaceBlockEntity.class); - when(grid.getActiveMachines(ECOMachineInterfaceBlockEntity.class)).thenReturn(java.util.Set.of(machineInterface)); + when(grid.getCraftingService()).thenReturn(service); var bus = createBus(node); - var first = mock(ECOCraftingPatternBusBlockEntity.class); - var second = mock(ECOCraftingPatternBusBlockEntity.class); - when(first.getPatternSlotCount()).thenReturn(2); - when(second.getPatternSlotCount()).thenReturn(3); - bus.bind(List.of(first, second), node); - assertEquals(5, bus.getPatternSlotCount()); - when(first.getPatternSlotCount()).thenReturn(3); - when(second.getPatternSlotCount()).thenReturn(2); - try (var providers = mockStatic(ICraftingProvider.class)) { - bus.tick(); - assertEquals(5, bus.getPatternSlotCount()); - verify(machineInterface).onPatternBusInventoryChanged(bus); - verify(machineInterface, never()).onPatternBusInventoryChanged(eq(bus), any(int[].class)); - providers.verify(() -> ICraftingProvider.requestUpdate(node)); - clearInvocations(machineInterface); - bus.tick(); - verifyNoInteractions(machineInterface); - } + bus.refreshAdvertisedPatterns(); + + verify(service).refreshNodeCraftingProvider(gridNode); } private static ExposedPatternBusBlockEntity createBus(IManagedGridNode node) { - try (var gridHelper = mockStatic(GridHelper.class); var config = mockStatic(NEConfig.class)) { - gridHelper.when(() -> GridHelper.createManagedNode(any(), any())).thenReturn(node); - config.when(NEConfig::getMaxCraftingPatternBusSlotCount).thenReturn(63); - config.when(NEConfig::getCraftingPatternBusPages).thenReturn(1); - var type = mock(BlockEntityType.class); - when(type.isValid(any())).thenReturn(true); - return new ExposedPatternBusBlockEntity(type, BlockPos.ZERO, - Blocks.AIR.defaultBlockState()); + var bus = mock(ExposedPatternBusBlockEntity.class, CALLS_REAL_METHODS); + setField(bus, "mergedPatternInventory", new CombinedPatternInventory()); + setField(bus, "buses", new ArrayList<>()); + bus.bind(List.of(), node); + return bus; + } + + private static void setField(Object target, String name, Object value) { + try { + var field = ExposedPatternBusBlockEntity.class.getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } catch (ReflectiveOperationException exception) { + throw new IllegalStateException(exception); } } } diff --git a/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventoryTest.java b/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventoryTest.java index d5063b29..13dc2d1d 100644 --- a/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventoryTest.java +++ b/src/test/java/com/sorrowmist/useless/compat/neoecoae/compact/provider/CombinedPatternInventoryTest.java @@ -1,11 +1,11 @@ package com.sorrowmist.useless.compat.neoecoae.compact.provider; import appeng.api.inventories.InternalInventory; -import appeng.util.inv.AppEngInternalInventory; import cn.dancingsnow.neoecoae.blocks.entity.crafting.ECOCraftingPatternBusBlockEntity; import com.sorrowmist.useless.InventoryTestBootstrap; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraftforge.items.ItemStackHandler; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -21,24 +21,17 @@ static void bootstrap() { } @Test - void managementReadsPhysicalSlotsEvenWhenTheTerminalHidesThem() { - var bus = mock(ECOCraftingPatternBusBlockEntity.class); - var raw = new AppEngInternalInventory(null, 2, 1); + void managementReadsAndWritesThePhysicalPatternSlots() throws ReflectiveOperationException { + var bus = createBus(); ItemStack disk = new ItemStack(Items.PAPER); - raw.setItemDirect(0, disk); - var terminal = mock(InternalInventory.class); - when(terminal.getStackInSlot(0)).thenReturn(ItemStack.EMPTY); - when(bus.getPatternSlotCount()).thenReturn(2); - when(bus.getPatternSlotInventory()).thenReturn(raw); - when(bus.getTerminalPatternInventory()).thenReturn(terminal); + bus.itemHandler.setStackInSlot(0, disk); var combined = new CombinedPatternInventory(); combined.bind(List.of(bus)); - assertSame(disk, combined.getStackInSlot(0)); + assertTrue(ItemStack.isSameItemSameTags(disk, combined.getStackInSlot(0))); assertEquals(1, combined.getSlotLimit(0)); combined.setItemDirect(1, disk); - verify(bus).setPatternDirect(1, disk); - verifyNoInteractions(terminal); + assertTrue(ItemStack.isSameItemSameTags(disk, combined.getStackInSlot(1))); } @Test @@ -70,9 +63,6 @@ void terminalIncludesAppendedRowsAndDelegatesTheirTakeAndInsertProtocol() { assertSame(pattern, terminal.insertItem(2, pattern, false)); terminal.setItemDirect(3, pattern); verify(secondView).setItemDirect(0, pattern); - verify(first, never()).setPatternDirect(anyInt(), any()); - verify(second, never()).setPatternDirect(anyInt(), any()); - // A real bus gaining pages must not shift the open menu's already published slot IDs. when(first.getPatternSlotCount()).thenReturn(4); assertEquals(6, combined.size()); @@ -80,4 +70,18 @@ void terminalIncludesAppendedRowsAndDelegatesTheirTakeAndInsertProtocol() { terminal.setItemDirect(3, ItemStack.EMPTY); verify(secondView).setItemDirect(0, ItemStack.EMPTY); } + + private static ECOCraftingPatternBusBlockEntity createBus() throws ReflectiveOperationException { + var bus = mock(ECOCraftingPatternBusBlockEntity.class); + var field = ECOCraftingPatternBusBlockEntity.class.getField("itemHandler"); + field.setAccessible(true); + field.set(bus, new ItemStackHandler(4) { + @Override + public int getSlotLimit(int slot) { + return 1; + } + }); + when(bus.getPatternSlotCount()).thenReturn(3); + return bus; + } }