diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 93a51dc..a2319f1 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -6,6 +6,7 @@ import com.extendedae_plus.init.ModBlockEntities; import com.extendedae_plus.init.ModBlocks; import com.extendedae_plus.init.ModCreativeTabs; import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.init.ModMenuTypes; import com.extendedae_plus.menu.locator.CuriosItemLocator; import com.extendedae_plus.network.ModNetwork; import net.minecraftforge.client.ConfigScreenHandler; @@ -21,6 +22,7 @@ import net.minecraft.resources.ResourceLocation; import com.extendedae_plus.client.ClientProxy; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; /** * ExtendedAE Plus 主mod类 @@ -30,13 +32,7 @@ public class ExtendedAEPlus { public static final String MODID = "extendedae_plus"; - // 在类加载时(尽可能早)在客户端注册内置模型,避免首次资源加载时错过。 - static { - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { - System.out.println("[ExtendedAE_Plus] Static init: register built-in models"); - ClientProxy.init(); - }); - } + // 注意:避免在静态初始化阶段访问注册对象,相关客户端注册改在 FMLClientSetupEvent 中执行。 public ExtendedAEPlus() { IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); @@ -49,6 +45,7 @@ public class ExtendedAEPlus { ModBlockEntities.BLOCK_ENTITY_TYPES.register(modEventBus); ModItems.ITEMS.register(modEventBus); ModCreativeTabs.TABS.register(modEventBus); + ModMenuTypes.MENUS.register(modEventBus); // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); @@ -56,11 +53,8 @@ public class ExtendedAEPlus { // 注册通用配置 ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); - // 构造期在客户端再确保一次注册(幂等) - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientProxy::init); - - // 在 Mods 菜单中注册配置界面入口(仅客户端,由 ClientProxy 执行以避免服务端类加载 Screen) - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientProxy::registerConfigScreen); + // 客户端侧延迟注册:在 FMLClientSetupEvent 阶段执行(包含 MenuScreens 绑定等) + modEventBus.addListener((FMLClientSetupEvent e) -> ClientProxy.onClientSetup(e)); } /** diff --git a/src/main/java/com/extendedae_plus/client/ClientProxy.java b/src/main/java/com/extendedae_plus/client/ClientProxy.java index 3a479e5..329450e 100644 --- a/src/main/java/com/extendedae_plus/client/ClientProxy.java +++ b/src/main/java/com/extendedae_plus/client/ClientProxy.java @@ -3,10 +3,14 @@ package com.extendedae_plus.client; import appeng.client.render.crafting.CraftingCubeModel; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider; +import com.extendedae_plus.client.screen.GlobalProviderModesScreen; +import com.extendedae_plus.init.ModMenuTypes; import com.extendedae_plus.content.crafting.EPlusCraftingUnitType; import com.extendedae_plus.hooks.BuiltInModelHooks; import net.minecraftforge.client.ConfigScreenHandler; import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraft.client.gui.screens.MenuScreens; /** * 客户端模型注册,将 formed 模型注册为内置模型。 @@ -39,6 +43,19 @@ public final class ClientProxy { BuiltInModelHooks.addBuiltInModel( ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"), new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x))); + + // 菜单 -> 屏幕 绑定 + MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new); + } + + /** + * 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。 + */ + public static void onClientSetup(final FMLClientSetupEvent event) { + event.enqueueWork(() -> { + init(); + registerConfigScreen(); + }); } /** diff --git a/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java new file mode 100644 index 0000000..c70a53f --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java @@ -0,0 +1,108 @@ +package com.extendedae_plus.client.screen; + +import com.extendedae_plus.menu.NetworkPatternControllerMenu; +import com.extendedae_plus.network.GlobalToggleProviderModesC2SPacket; +import com.extendedae_plus.network.ModNetwork; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; + +public class GlobalProviderModesScreen extends AbstractContainerScreen { + private static final Component CUSTOM_TITLE = Component.literal("样板供应器状态控制器"); + public GlobalProviderModesScreen(NetworkPatternControllerMenu menu, Inventory inv, Component title) { + super(menu, inv, title); + this.imageWidth = 240; + this.imageHeight = 140; + } + + @Override + protected void init() { + super.init(); + int w = 70; // 按钮宽 + int h = 20; // 按钮高 + int s = 8; // 按钮间距 + int y = this.topPos + 28; // 第一行 Y + // 计算三列按钮的左侧起点,使其在面板内水平居中 + int totalW3 = w * 3 + s * 2; + int x = this.leftPos + (this.imageWidth - totalW3) / 2; + + // 行1:三个单项切换 + addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_blocking"), b -> + ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket( + GlobalToggleProviderModesC2SPacket.Op.TOGGLE, + GlobalToggleProviderModesC2SPacket.Op.NOOP, + GlobalToggleProviderModesC2SPacket.Op.NOOP, + this.menu.getBlockEntityPos() + ))).bounds(x, y, w, h).build()); + + addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_adv_blocking"), b -> + ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket( + GlobalToggleProviderModesC2SPacket.Op.NOOP, + GlobalToggleProviderModesC2SPacket.Op.TOGGLE, + GlobalToggleProviderModesC2SPacket.Op.NOOP, + this.menu.getBlockEntityPos() + ))).bounds(x + w + s, y, w, h).build()); + + addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_smart_doubling"), b -> + ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket( + GlobalToggleProviderModesC2SPacket.Op.NOOP, + GlobalToggleProviderModesC2SPacket.Op.NOOP, + GlobalToggleProviderModesC2SPacket.Op.TOGGLE, + this.menu.getBlockEntityPos() + ))).bounds(x + (w + s) * 2, y, w, h).build()); + + // 行2:一键全开/全关 + int y2 = y + h + 12; + // 第二行:两列按钮,总宽并居中 + int totalW2 = w * 2 + s; + int x2 = this.leftPos + (this.imageWidth - totalW2) / 2; + addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.all_on"), b -> + ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket( + GlobalToggleProviderModesC2SPacket.Op.SET_TRUE, + GlobalToggleProviderModesC2SPacket.Op.SET_TRUE, + GlobalToggleProviderModesC2SPacket.Op.SET_TRUE, + this.menu.getBlockEntityPos() + ))).bounds(x2, y2, w, h).build()); + + addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.all_off"), b -> + ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket( + GlobalToggleProviderModesC2SPacket.Op.SET_FALSE, + GlobalToggleProviderModesC2SPacket.Op.SET_FALSE, + GlobalToggleProviderModesC2SPacket.Op.SET_FALSE, + this.menu.getBlockEntityPos() + ))).bounds(x2 + w + s, y2, w, h).build()); + } + + @Override + protected void renderBg(net.minecraft.client.gui.GuiGraphics gfx, float partialTicks, int mouseX, int mouseY) { + // 半透明全屏遮罩,避免底层 HUD(准星/物品栏文字)透出 + gfx.fill(0, 0, this.width, this.height, 0xC0000000); + + // 在按钮区域绘制一个半透明面板,提升可读性 + int pad = 6; + int panelLeft = this.leftPos - pad; + int panelTop = this.topPos - pad; + int panelRight = this.leftPos + this.imageWidth + pad; + int panelBottom = this.topPos + this.imageHeight + pad; + gfx.fill(panelLeft, panelTop, panelRight, panelBottom, 0xA01E1E1E); + // 边框 + gfx.fill(panelLeft, panelTop, panelRight, panelTop + 1, 0x80FFFFFF); + gfx.fill(panelLeft, panelBottom - 1, panelRight, panelBottom, 0x80000000); + gfx.fill(panelLeft, panelTop, panelLeft + 1, panelBottom, 0x80FFFFFF); + gfx.fill(panelRight - 1, panelTop, panelRight, panelBottom, 0x80000000); + } + + @Override + public void render(net.minecraft.client.gui.GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(gfx); + super.render(gfx, mouseX, mouseY, partialTicks); + gfx.drawString(this.font, CUSTOM_TITLE, this.leftPos + 10, this.topPos + 8, 0xFFFFFF, false); + } + + @Override + protected void renderLabels(net.minecraft.client.gui.GuiGraphics gfx, int mouseX, int mouseY) { + // 不绘制默认的玩家物品栏标题(例如“物品栏”),避免与自定义面板重叠 + // 标题已在 render() 中手动绘制 + } +} diff --git a/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlock.java b/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlock.java new file mode 100644 index 0000000..d77e568 --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlock.java @@ -0,0 +1,38 @@ +package com.extendedae_plus.content.controller; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +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.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraftforge.network.NetworkHooks; + +public class NetworkPatternControllerBlock extends Block implements EntityBlock { + public NetworkPatternControllerBlock(Properties props) { + super(props); + } + + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return new NetworkPatternControllerBlockEntity(pos, state); + } + + @Override + public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + if (!level.isClientSide) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof MenuProvider provider && player instanceof ServerPlayer sp) { + NetworkHooks.openScreen(sp, provider, pos); + return InteractionResult.CONSUME; + } + } + return InteractionResult.sidedSuccess(level.isClientSide); + } +} diff --git a/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlockEntity.java b/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlockEntity.java new file mode 100644 index 0000000..0dff6a5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/controller/NetworkPatternControllerBlockEntity.java @@ -0,0 +1,92 @@ +package com.extendedae_plus.content.controller; + +import appeng.api.networking.GridHelper; +import appeng.api.networking.GridFlags; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IInWorldGridNodeHost; +import appeng.api.networking.IManagedGridNode; +import com.extendedae_plus.init.ModBlockEntities; +import com.extendedae_plus.init.ModMenuTypes; +import com.extendedae_plus.menu.NetworkPatternControllerMenu; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +public class NetworkPatternControllerBlockEntity extends BlockEntity implements IInWorldGridNodeHost, MenuProvider { + + private final IManagedGridNode managedNode; + + public NetworkPatternControllerBlockEntity(BlockPos pos, BlockState state) { + super(ModBlockEntities.NETWORK_PATTERN_CONTROLLER_BE.get(), pos, state); + this.managedNode = GridHelper.createManagedNode(this, NodeListener.INSTANCE); + this.managedNode.setIdlePowerUsage(1.0); + this.managedNode.setInWorldNode(true); + this.managedNode.setFlags(GridFlags.REQUIRE_CHANNEL); + this.managedNode.setTagName("network_pattern_controller"); + } + + @Override + public @Nullable IGridNode getGridNode(@Nullable Direction dir) { + return managedNode == null ? null : managedNode.getNode(); + } + + @Override + public void onLoad() { + super.onLoad(); + if (this.level != null && !this.level.isClientSide) { + GridHelper.onFirstTick(this, be -> be.managedNode.create(be.getLevel(), be.getBlockPos())); + } + } + + @Override + protected void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + this.managedNode.saveToNBT(tag); + } + + @Override + public void load(CompoundTag tag) { + super.load(tag); + this.managedNode.loadFromNBT(tag); + } + + @Override + public void onChunkUnloaded() { + super.onChunkUnloaded(); + this.managedNode.destroy(); + } + + @Override + public void setRemoved() { + super.setRemoved(); + this.managedNode.destroy(); + } + + @Override + public Component getDisplayName() { + return Component.translatable("block.extendedae_plus.network_pattern_controller"); + } + + @Override + public AbstractContainerMenu createMenu(int id, Inventory inv, Player player) { + return new NetworkPatternControllerMenu(id, inv, this.worldPosition); + } + + enum NodeListener implements IGridNodeListener { + INSTANCE; + @Override + public void onSaveChanges(NetworkPatternControllerBlockEntity host, IGridNode node) { + host.setChanged(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java index a82eb16..7db517e 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java +++ b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java @@ -2,6 +2,7 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; +import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; @@ -17,4 +18,9 @@ public final class ModBlockEntities { BLOCK_ENTITY_TYPES.register("wireless_transceiver", () -> BlockEntityType.Builder.of(WirelessTransceiverBlockEntity::new, ModBlocks.WIRELESS_TRANSCEIVER.get()).build(null)); + + public static final RegistryObject> NETWORK_PATTERN_CONTROLLER_BE = + BLOCK_ENTITY_TYPES.register("network_pattern_controller", + () -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new, + ModBlocks.NETWORK_PATTERN_CONTROLLER.get()).build(null)); } diff --git a/src/main/java/com/extendedae_plus/init/ModBlocks.java b/src/main/java/com/extendedae_plus/init/ModBlocks.java index db87bbb..4316e2a 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlocks.java +++ b/src/main/java/com/extendedae_plus/init/ModBlocks.java @@ -28,6 +28,17 @@ public final class ModBlocks { ) ); + // AE2 网络模式控制器方块 + public static final RegistryObject NETWORK_PATTERN_CONTROLLER = BLOCKS.register( + "network_pattern_controller", + () -> new com.extendedae_plus.content.controller.NetworkPatternControllerBlock( + BlockBehaviour.Properties.of() + .mapColor(MapColor.METAL) + .strength(1.5F, 6.0F) + .requiresCorrectToolForDrops() + ) + ); + // Crafting Accelerators (reuse MAE2 textures/models) public static final RegistryObject ACCELERATOR_4x = BLOCKS.register( "4x_crafting_accelerator", diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index 6218e93..e48a376 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -20,6 +20,7 @@ public final class ModCreativeTabs { .displayItems((params, output) -> { // 将本模组物品加入创造物品栏 output.accept(ModItems.WIRELESS_TRANSCEIVER.get()); + output.accept(ModItems.NETWORK_PATTERN_CONTROLLER.get()); output.accept(ModItems.ACCELERATOR_4x.get()); output.accept(ModItems.ACCELERATOR_16x.get()); output.accept(ModItems.ACCELERATOR_64x.get()); diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index 150c5e1..d2d290f 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -17,6 +17,11 @@ public final class ModItems { () -> new BlockItem(ModBlocks.WIRELESS_TRANSCEIVER.get(), new Item.Properties()) ); + public static final RegistryObject NETWORK_PATTERN_CONTROLLER = ITEMS.register( + "network_pattern_controller", + () -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties()) + ); + // Crafting Accelerators public static final RegistryObject ACCELERATOR_4x = ITEMS.register( "4x_crafting_accelerator", diff --git a/src/main/java/com/extendedae_plus/init/ModMenuTypes.java b/src/main/java/com/extendedae_plus/init/ModMenuTypes.java new file mode 100644 index 0000000..47a0936 --- /dev/null +++ b/src/main/java/com/extendedae_plus/init/ModMenuTypes.java @@ -0,0 +1,20 @@ +package com.extendedae_plus.init; + +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.menu.NetworkPatternControllerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraftforge.common.extensions.IForgeMenuType; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +public final class ModMenuTypes { + private ModMenuTypes() {} + + public static final DeferredRegister> MENUS = + DeferredRegister.create(ForgeRegistries.MENU_TYPES, ExtendedAEPlus.MODID); + + public static final RegistryObject> NETWORK_PATTERN_CONTROLLER = + MENUS.register("network_pattern_controller", + () -> IForgeMenuType.create(NetworkPatternControllerMenu::new)); +} diff --git a/src/main/java/com/extendedae_plus/menu/NetworkPatternControllerMenu.java b/src/main/java/com/extendedae_plus/menu/NetworkPatternControllerMenu.java new file mode 100644 index 0000000..3c954c5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/menu/NetworkPatternControllerMenu.java @@ -0,0 +1,33 @@ +package com.extendedae_plus.menu; + +import com.extendedae_plus.init.ModMenuTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; + +public class NetworkPatternControllerMenu extends AbstractContainerMenu { + private final BlockPos bePos; + + public NetworkPatternControllerMenu(int id, Inventory inv, BlockPos bePos) { + super(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), id); + this.bePos = bePos; + } + + public NetworkPatternControllerMenu(int id, Inventory inv, FriendlyByteBuf buf) { + this(id, inv, buf.readBlockPos()); + } + + public BlockPos getBlockEntityPos() { return bePos; } + + @Override + public boolean stillValid(Player player) { return true; } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + // 无物品槽的容器,直接返回空堆以禁用快速转移 + return ItemStack.EMPTY; + } +} diff --git a/src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java b/src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java new file mode 100644 index 0000000..c37d0bc --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java @@ -0,0 +1,208 @@ +package com.extendedae_plus.network; + +import appeng.api.config.Settings; +import appeng.api.config.YesNo; +import appeng.api.networking.IGrid; +import appeng.blockentity.crafting.PatternProviderBlockEntity; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.parts.crafting.PatternProviderPart; +import com.extendedae_plus.api.AdvancedBlockingHolder; +import com.extendedae_plus.api.SmartDoublingHolder; +import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.Set; +import java.util.HashSet; +import java.util.function.Supplier; + +/** + * C2S:全网批量切换样板供应器的三种模式: + * - 阻挡模式(AE2 内置 BLOCKING_MODE 设置) + * - 高级阻挡模式(AdvancedBlockingHolder mixin) + * - 智能翻倍模式(SmartDoublingHolder mixin) + * + * 负载为三个操作码(各1字节),分别对应:blocking、advancedBlocking、smartDoubling。 + */ +public class GlobalToggleProviderModesC2SPacket { + public enum Op { + NOOP((byte) 0), + SET_TRUE((byte) 1), + SET_FALSE((byte) 2), + TOGGLE((byte) 3); + public final byte id; + Op(byte id) { this.id = id; } + public static Op byId(byte id) { + return switch (id) { + case 1 -> SET_TRUE; + case 2 -> SET_FALSE; + case 3 -> TOGGLE; + default -> NOOP; + }; + } + } + + private final Op opBlocking; + private final Op opAdvancedBlocking; + private final Op opSmartDoubling; + private final BlockPos controllerPos; + + public GlobalToggleProviderModesC2SPacket(Op opBlocking, Op opAdvancedBlocking, Op opSmartDoubling, BlockPos controllerPos) { + this.opBlocking = opBlocking; + this.opAdvancedBlocking = opAdvancedBlocking; + this.opSmartDoubling = opSmartDoubling; + this.controllerPos = controllerPos; + } + + public static void encode(GlobalToggleProviderModesC2SPacket msg, FriendlyByteBuf buf) { + buf.writeByte(msg.opBlocking.id); + buf.writeByte(msg.opAdvancedBlocking.id); + buf.writeByte(msg.opSmartDoubling.id); + buf.writeBlockPos(msg.controllerPos); + } + + public static GlobalToggleProviderModesC2SPacket decode(FriendlyByteBuf buf) { + Op b = Op.byId(buf.readByte()); + Op ab = Op.byId(buf.readByte()); + Op sd = Op.byId(buf.readByte()); + BlockPos pos = buf.readBlockPos(); + return new GlobalToggleProviderModesC2SPacket(b, ab, sd, pos); + } + + public static void handle(GlobalToggleProviderModesC2SPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + ServerPlayer player = ctx.getSender(); + if (player == null) return; + + // 从控制方块实体的 AE2 节点确定 AE 网络上下文 + var level = player.serverLevel(); + var be = level.getBlockEntity(msg.controllerPos); + if (!(be instanceof NetworkPatternControllerBlockEntity controller)) return; + var node = controller.getGridNode(null); + if (node == null) return; + IGrid grid = node.getGrid(); + if (grid == null) return; + + int affected = applyToAllProviders(grid, msg); + // 向发起玩家反馈影响数量,便于判断按钮是否生效 + player.displayClientMessage(Component.literal("E+ 全局切换已应用到 " + affected + " 个样板供应器"), true); + }); + ctx.setPacketHandled(true); + } + + private static int applyToAllProviders(IGrid grid, GlobalToggleProviderModesC2SPacket msg) { + int affected = 0; + // 去重集合,避免同一逻辑重复计数 + Set all = new HashSet<>(); + + // 方块形式的样板供应器(全部/在线) + try { + Set blocksAll = grid.getMachines(PatternProviderBlockEntity.class); + Set blocksActive = grid.getActiveMachines(PatternProviderBlockEntity.class); + for (PatternProviderBlockEntity be : blocksAll) if (be != null && be.getLogic() != null) all.add(be.getLogic()); + for (PatternProviderBlockEntity be : blocksActive) if (be != null && be.getLogic() != null) all.add(be.getLogic()); + } catch (Throwable ignored) {} + + // Part 形式的样板供应器(全部/在线) + try { + Set partsAll = grid.getMachines(PatternProviderPart.class); + Set partsActive = grid.getActiveMachines(PatternProviderPart.class); + for (PatternProviderPart part : partsAll) if (part != null && part.getLogic() != null) all.add(part.getLogic()); + for (PatternProviderPart part : partsActive) if (part != null && part.getLogic() != null) all.add(part.getLogic()); + } catch (Throwable ignored) {} + + // 兼容:任意实现了 PatternProviderLogicHost 的机器(例如 ExtendedAE 的 PartExPatternProvider) + try { + Set hostsAll = grid.getMachines(PatternProviderLogicHost.class); + Set hostsActive = grid.getActiveMachines(PatternProviderLogicHost.class); + for (PatternProviderLogicHost host : hostsAll) if (host != null && host.getLogic() != null) all.add(host.getLogic()); + for (PatternProviderLogicHost host : hostsActive) if (host != null && host.getLogic() != null) all.add(host.getLogic()); + } catch (Throwable ignored) {} + + // 兼容:显式匹配第三方具体类(通过反射),避免 AE2 仅按精确类型匹配导致 interface 不返回的问题 + collectByClassName(grid, all, "com.glodblock.github.extendedae.common.parts.PartExPatternProvider"); + collectByClassName(grid, all, "com.glodblock.github.extendedae.common.tileentities.TileExPatternProvider"); + + for (PatternProviderLogic logic : all) { + if (applyToLogic(logic, msg)) affected++; + } + return affected; + } + + private static void collectByClassName(IGrid grid, Set out, String className) { + try { + Class cls = Class.forName(className); + // 收集全部与在线两类机器 + Set all = grid.getMachines((Class) cls); + Set active = grid.getActiveMachines((Class) cls); + for (Object o : all) addLogicIfPresent(out, o); + for (Object o : active) addLogicIfPresent(out, o); + } catch (Throwable ignored) {} + } + + private static void addLogicIfPresent(Set out, Object o) { + try { + if (o instanceof PatternProviderLogicHost host) { + var logic = host.getLogic(); + if (logic != null) out.add(logic); + return; + } + // 兜底:若对象有 getLogic 方法且返回 PatternProviderLogic + var m = o.getClass().getMethod("getLogic"); + Object ret = m.invoke(o); + if (ret instanceof PatternProviderLogic logic) out.add(logic); + } catch (Throwable ignored) {} + } + + private static boolean applyToLogic(PatternProviderLogic logic, GlobalToggleProviderModesC2SPacket msg) { + if (logic == null) return false; + boolean changed = false; + // 1) 阻挡模式(AE2 内置设置) + if (msg.opBlocking != Op.NOOP) { + boolean current = safeIsBlocking(logic); + boolean target = computeTarget(current, msg.opBlocking); + var cm = logic.getConfigManager(); + if (cm != null) { + cm.putSetting(Settings.BLOCKING_MODE, target ? YesNo.YES : YesNo.NO); + changed = changed || (current != target); + } + } + // 2) 高级阻挡(mixin 接口) + if (msg.opAdvancedBlocking != Op.NOOP && logic instanceof AdvancedBlockingHolder adv) { + boolean current = adv.eap$getAdvancedBlocking(); + boolean target = computeTarget(current, msg.opAdvancedBlocking); + adv.eap$setAdvancedBlocking(target); + changed = changed || (current != target); + } + // 3) 智能翻倍(mixin 接口) + if (msg.opSmartDoubling != Op.NOOP && logic instanceof SmartDoublingHolder sd) { + boolean current = sd.eap$getSmartDoubling(); + boolean target = computeTarget(current, msg.opSmartDoubling); + sd.eap$setSmartDoubling(target); + changed = changed || (current != target); + } + // 保存更改并让 AE2 同步 + if (changed) { + try { logic.saveChanges(); } catch (Throwable ignored) {} + } + return changed; + } + + private static boolean computeTarget(boolean current, Op op) { + return switch (op) { + case SET_TRUE -> true; + case SET_FALSE -> false; + case TOGGLE -> !current; + default -> current; + }; + } + + private static boolean safeIsBlocking(PatternProviderLogic logic) { + try { return logic.isBlocking(); } catch (Throwable t) { return false; } + } +} diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/network/ModNetwork.java index 2143246..a869cfe 100644 --- a/src/main/java/com/extendedae_plus/network/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/network/ModNetwork.java @@ -72,6 +72,12 @@ public class ModNetwork { .consumerNetworkThread(ToggleSmartDoublingC2SPacket::handle) .add(); + CHANNEL.messageBuilder(GlobalToggleProviderModesC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(GlobalToggleProviderModesC2SPacket::encode) + .decoder(GlobalToggleProviderModesC2SPacket::decode) + .consumerNetworkThread(GlobalToggleProviderModesC2SPacket::handle) + .add(); + CHANNEL.messageBuilder(AdvancedBlockingSyncS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT) .encoder(AdvancedBlockingSyncS2CPacket::encode) .decoder(AdvancedBlockingSyncS2CPacket::decode) diff --git a/src/main/resources/assets/extendedae_plus/blockstates/network_pattern_controller.json b/src/main/resources/assets/extendedae_plus/blockstates/network_pattern_controller.json new file mode 100644 index 0000000..6b2af88 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/blockstates/network_pattern_controller.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "extendedae_plus:block/network_pattern_controller" } + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 5235641..e8db96c 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -43,5 +43,12 @@ "config.extendedae_plus.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)", "config.extendedae_plus.wirelessMaxRange": "无线最大距离", "config.extendedae_plus.wirelessMaxRange_with_range": "无线最大距离 (1-4096)", - "config.extendedae_plus.wirelessCrossDimEnable": "无线收发器允许跨维度连接" + "config.extendedae_plus.wirelessCrossDimEnable": "无线收发器允许跨维度连接", + "block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器", + "item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器", + "gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式", + "gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡", + "gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍", + "gui.extendedae_plus.global.all_on": "全部开启", + "gui.extendedae_plus.global.all_off": "全部关闭" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/models/block/network_pattern_controller.json b/src/main/resources/assets/extendedae_plus/models/block/network_pattern_controller.json new file mode 100644 index 0000000..3a3c837 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/block/network_pattern_controller.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "extendedae_plus:block/network_pattern_controller" + } +} diff --git a/src/main/resources/assets/extendedae_plus/models/item/network_pattern_controller.json b/src/main/resources/assets/extendedae_plus/models/item/network_pattern_controller.json new file mode 100644 index 0000000..a439891 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/item/network_pattern_controller.json @@ -0,0 +1,3 @@ +{ + "parent": "extendedae_plus:block/network_pattern_controller" +} diff --git a/src/main/resources/assets/extendedae_plus/textures/block/network_pattern_controller.png b/src/main/resources/assets/extendedae_plus/textures/block/network_pattern_controller.png new file mode 100644 index 0000000..bcfbe84 Binary files /dev/null and b/src/main/resources/assets/extendedae_plus/textures/block/network_pattern_controller.png differ diff --git a/src/main/resources/data/extendedae_plus/recipes/network_pattern_controller.json b/src/main/resources/data/extendedae_plus/recipes/network_pattern_controller.json new file mode 100644 index 0000000..38721c7 --- /dev/null +++ b/src/main/resources/data/extendedae_plus/recipes/network_pattern_controller.json @@ -0,0 +1,12 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "ae2:semi_dark_monitor" }, + { "item": "ae2:pattern_provider" }, + { "item": "ae2:network_tool" } + ], + "result": { + "item": "extendedae_plus:network_pattern_controller", + "count": 1 + } +}