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;
+ }
}