diff --git a/build.gradle b/build.gradle index 35ff944..c52f6ea 100644 --- a/build.gradle +++ b/build.gradle @@ -125,6 +125,47 @@ neoForge { // Include resources generated by data generators. sourceSets.main.resources { srcDir 'src/generated/resources' } +// 暂时排除缺少依赖的可选联动源码,待补齐依赖后再启用 +sourceSets.main.java { + // 广泛屏蔽迁移中的旧源码,仅保留模板核心类(ExtendedAEPlus、ExtendedAEPlusClient、Config) + exclude 'com/extendedae_plus/NewIcon.java' + // 注意:不要屏蔽 api/**,我们有选择性 include 的接口文件需要参与编译 + include 'com/extendedae_plus/api/**' + exclude 'com/extendedae_plus/client/**' + // 包含配置包 + include 'com/extendedae_plus/config/**' + exclude 'com/extendedae_plus/content/**' + exclude 'com/extendedae_plus/hooks/**' + exclude 'com/extendedae_plus/init/**' + exclude 'com/extendedae_plus/integration/**' + exclude 'com/extendedae_plus/menu/**' + // 包含主 Mod 类 + include 'com/extendedae_plus/ExtendedAEPlus.java' + // 仅保留 mixin 中的 accessor 参与编译 + // 解除对 accessor 的屏蔽 + include 'com/extendedae_plus/mixin/ae2/accessor/**' + // GUI 方案A:解禁最小 GUI 混入 + include 'com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java' + include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java' + include 'com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java' + // 注意:不要屏蔽 network/**,我们已选择性 include 需要的网络文件 + // 仅解禁样板倍增核心所需的两个 util 文件 + include 'com/extendedae_plus/util/PatternProviderDataUtil.java' + include 'com/extendedae_plus/util/PatternProviderUIHelper.java' + // GUI 与网络最小依赖 + // 全量包含 api 包,防止个别接口遗漏 + include 'com/extendedae_plus/api/**' + include 'com/extendedae_plus/util/ExtendedAELogger.java' + include 'com/extendedae_plus/network/ModNetwork.java' + include 'com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java' + include 'com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java' + // 仅 include 需要的 util 文件(不再对 util/** 做全局排除,以免误伤) + include 'com/extendedae_plus/util/PatternProviderDataUtil.java' + include 'com/extendedae_plus/util/PatternProviderUIHelper.java' + include 'com/extendedae_plus/util/ExtendedAELogger.java' + exclude 'com/extendedae_plus/wireless/**' +} + // Sets up a dependency configuration called 'localRuntime'. // This configuration should be used instead of 'runtimeOnly' to declare // a dependency that will be present for runtime testing but that is @@ -163,6 +204,7 @@ dependencies { // jarJar configuration not set in this build; use implementation for API for now implementation "de.mari_023:ae2wtlib_api:19.2.0" implementation "curse.maven:ex-pattern-provider-892005:6863556" + implementation "curse.maven:curios-309927:6529130" compileOnly "curse.maven:applied-flux-965012:5614830" compileOnly "dev.emi:emi-neoforge:1.1.10+1.21" diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 1ce6911..868eadc 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,7 +3,10 @@ package com.extendedae_plus; import org.slf4j.Logger; import com.mojang.logging.LogUtils; +import com.extendedae_plus.config.ModConfigs; +import com.extendedae_plus.network.ModNetwork; +import net.minecraft.resources.ResourceLocation; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; @@ -56,7 +59,7 @@ public class ExtendedAEPlus { // Creates a creative tab with the id "extendedaeplus:example_tab" for the example item, that is placed after the combat tab public static final DeferredHolder EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder() - .title(Component.translatable("itemGroup.extendedaeplus")) //The language key for the title of your CreativeModeTab + .title(Component.translatable("itemGroup.extendedae_plus")) //The language key for the title of your CreativeModeTab .withTabsBefore(CreativeModeTabs.COMBAT) .icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) .displayItems((parameters, output) -> { @@ -84,21 +87,23 @@ public class ExtendedAEPlus { // Register the item to a creative tab modEventBus.addListener(this::addCreative); - // Register our mod's ModConfigSpec so that FML can create and load the config file for us - modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC); + // 注册配置:接入自定义的 ModConfigs + modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); + } + + // 便捷 ResourceLocation 工具 + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MODID, path); } private void commonSetup(FMLCommonSetupEvent event) { // Some common setup code LOGGER.info("HELLO FROM COMMON SETUP"); + // 示例日志,避免引用不存在的模板 Config 字段 + LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); - if (Config.LOG_DIRT_BLOCK.getAsBoolean()) { - LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); - } - - LOGGER.info("{}{}", Config.MAGIC_NUMBER_INTRODUCTION.get(), Config.MAGIC_NUMBER.getAsInt()); - - Config.ITEM_STRINGS.get().forEach((item) -> LOGGER.info("ITEM >> {}", item)); + // 注册网络通道 + event.enqueueWork(ModNetwork::register); } // Add the example block item to the building blocks tab @@ -115,3 +120,4 @@ public class ExtendedAEPlus { LOGGER.info("HELLO from server starting"); } } + diff --git a/src/main/java/com/extendedae_plus/NewIcon.java b/src/main/java/com/extendedae_plus/NewIcon.java new file mode 100644 index 0000000..8535a1f --- /dev/null +++ b/src/main/java/com/extendedae_plus/NewIcon.java @@ -0,0 +1,28 @@ +package com.extendedae_plus; + +import appeng.client.gui.style.Blitter; +import net.minecraft.resources.ResourceLocation; + +public class NewIcon { + @SuppressWarnings("all") + private static final ResourceLocation TEXTURE = new ResourceLocation(ExtendedAEPlus.MODID,"textures/gui/nicons.png"); + + + + public static final Blitter MULTIPLY2; + public static final Blitter DIVIDE2; + public static final Blitter MULTIPLY5; + public static final Blitter DIVIDE5; + public static final Blitter MULTIPLY10; + public static final Blitter DIVIDE10; + + static { + MULTIPLY2 = Blitter.texture(TEXTURE, 64, 64).src(32, 0, 16, 16); + DIVIDE2 = Blitter.texture(TEXTURE, 64, 64).src(48, 0, 16, 16); + MULTIPLY5 = Blitter.texture(TEXTURE, 64, 64).src(0, 0, 16, 16); + DIVIDE5 = Blitter.texture(TEXTURE, 64, 64).src(16, 0, 16, 16); + MULTIPLY10 = Blitter.texture(TEXTURE, 64, 64).src(0, 16, 16, 16); + DIVIDE10 = Blitter.texture(TEXTURE, 64, 64).src(16, 16, 16, 16); + + } +} diff --git a/src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java b/src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java new file mode 100644 index 0000000..52b8aaa --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java @@ -0,0 +1,7 @@ +package com.extendedae_plus.api; + +public interface AdvancedBlockingHolder { + boolean eap$getAdvancedBlocking(); + void eap$setAdvancedBlocking(boolean value); + +} diff --git a/src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java b/src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java new file mode 100644 index 0000000..d97f448 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java @@ -0,0 +1,11 @@ +package com.extendedae_plus.api; + +/** + * 由 {@code GuiExPatternProviderMixin} 实现,用于从通用的 Screen Mixin 中更新按钮布局。 + */ +public interface ExPatternButtonsAccessor { + /** + * 在每帧调用以维护扩展样板供应器右侧按钮的可见性、重注册(窗口尺寸变化)与定位。 + */ + void eap$updateButtonsLayout(); +} diff --git a/src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java b/src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java new file mode 100644 index 0000000..fca633f --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java @@ -0,0 +1,8 @@ +package com.extendedae_plus.api; + +/** + * 由 GuiExPatternProviderMixin 实现,用于在客户端侧提供当前页号,避免反射读取 AE2 内部字段失败。 + */ +public interface ExPatternPageAccessor { + int eap$getCurrentPage(); +} diff --git a/src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java b/src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java new file mode 100644 index 0000000..f43f52c --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java @@ -0,0 +1,5 @@ +package com.extendedae_plus.api; + +public interface PatternProviderMenuAdvancedSync { + boolean eap$getAdvancedBlockingSynced(); +} diff --git a/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java b/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java new file mode 100644 index 0000000..a9d1492 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java @@ -0,0 +1,5 @@ +package com.extendedae_plus.api; + +public interface PatternProviderMenuDoublingSync { + boolean eap$getSmartDoublingSynced(); +} diff --git a/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java b/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java new file mode 100644 index 0000000..5c18658 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java @@ -0,0 +1,6 @@ +package com.extendedae_plus.api; + +public interface SmartDoublingAwarePattern { + boolean eap$allowScaling(); + void eap$setAllowScaling(boolean allow); +} diff --git a/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java b/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java new file mode 100644 index 0000000..b5af4ef --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java @@ -0,0 +1,6 @@ +package com.extendedae_plus.api; + +public interface SmartDoublingHolder { + boolean eap$getSmartDoubling(); + void eap$setSmartDoubling(boolean value); +} diff --git a/src/main/java/com/extendedae_plus/client/ClientAdvancedBlockingState.java b/src/main/java/com/extendedae_plus/client/ClientAdvancedBlockingState.java new file mode 100644 index 0000000..0dc11b1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ClientAdvancedBlockingState.java @@ -0,0 +1,26 @@ +package com.extendedae_plus.client; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class ClientAdvancedBlockingState { + private static final Map states = new ConcurrentHashMap<>(); + + private ClientAdvancedBlockingState() {} + + public static String key(String dimension, long blockPosLong) { + return dimension + "@" + blockPosLong; + } + + public static void set(String key, boolean v) { + states.put(key, v); + } + + public static boolean has(String key) { + return states.containsKey(key); + } + + public static boolean get(String key) { + return states.getOrDefault(key, false); + } +} diff --git a/src/main/java/com/extendedae_plus/client/ClientModelEvents.java b/src/main/java/com/extendedae_plus/client/ClientModelEvents.java new file mode 100644 index 0000000..579f02b --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ClientModelEvents.java @@ -0,0 +1,27 @@ +package com.extendedae_plus.client; + +import com.extendedae_plus.ExtendedAEPlus; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ModelEvent; + +/** + * 确保在模型烘焙/资源重载期间也会注册内置模型,避免在刷新资源后丢失内置模型映射。 + */ +@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) +public final class ClientModelEvents { + private ClientModelEvents() {} + + @SubscribeEvent + public static void onRegisterAdditional(ModelEvent.RegisterAdditional event) { + // 在每次模型重载开始时确保内置模型已注册 + // 先显式登记这些模型ID,使其在首次加载阶段被请求,从而触发我们的内置模型拦截 + event.register(ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2")); + event.register(ExtendedAEPlus.id("block/crafting/16x_accelerator_formed_v2")); + event.register(ExtendedAEPlus.id("block/crafting/64x_accelerator_formed_v2")); + event.register(ExtendedAEPlus.id("block/crafting/256x_accelerator_formed_v2")); + event.register(ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2")); + ClientProxy.init(); + } +} diff --git a/src/main/java/com/extendedae_plus/client/ClientProxy.java b/src/main/java/com/extendedae_plus/client/ClientProxy.java new file mode 100644 index 0000000..745e3e4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ClientProxy.java @@ -0,0 +1,57 @@ +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.neoforged.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraft.client.gui.screens.MenuScreens; + +/** + * 客户端模型注册,将 formed 模型注册为内置模型。 + */ +public final class ClientProxy { + private ClientProxy() {} + + private static boolean REGISTERED = false; + + public static void init() { + if (REGISTERED) return; + REGISTERED = true; + // 注册四种形成态模型为内置模型 + BuiltInModelHooks.addBuiltInModel( + ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"), + new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_4x))); + + BuiltInModelHooks.addBuiltInModel( + ExtendedAEPlus.id("block/crafting/16x_accelerator_formed_v2"), + new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_16x))); + + BuiltInModelHooks.addBuiltInModel( + ExtendedAEPlus.id("block/crafting/64x_accelerator_formed_v2"), + new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_64x))); + + BuiltInModelHooks.addBuiltInModel( + ExtendedAEPlus.id("block/crafting/256x_accelerator_formed_v2"), + new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_256x))); + + BuiltInModelHooks.addBuiltInModel( + ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"), + new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x))); + } + + /** + * 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。 + */ + public static void onClientSetup(final FMLClientSetupEvent event) { + event.enqueueWork(() -> { + // 确保在首次资源加载前完成内置模型注册(REGISTERED 保护避免重复) + init(); + // 菜单 -> 屏幕 绑定 + MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new); + }); + } +} diff --git a/src/main/java/com/extendedae_plus/client/InputEvents.java b/src/main/java/com/extendedae_plus/client/InputEvents.java new file mode 100644 index 0000000..1264b96 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/InputEvents.java @@ -0,0 +1,126 @@ +package com.extendedae_plus.client; + +import appeng.api.stacks.GenericStack; +import appeng.client.gui.me.common.MEStorageScreen; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.integration.jei.JeiRuntimeProxy; +import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor; +import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.network.OpenCraftFromJeiC2SPacket; +import com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket; +import mezz.jei.api.ingredients.ITypedIngredient; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.world.item.ItemStack; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.ScreenEvent; +import org.lwjgl.glfw.GLFW; + +import java.util.Optional; + +@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT) +public final class InputEvents { + private InputEvents() {} + + // 临时适配:在缺少 AE2 的 JEI 辅助类时,仅尝试从 JEI 提供的原生 ItemStack 获取;否则不处理。 + private static GenericStack toGenericStack(ITypedIngredient typed) { + try { + Optional maybe = typed.getItemStack(); + if (maybe.isPresent()) { + ItemStack is = maybe.get(); + // 尝试使用 AE2 的通用构造(若不可用则返回 null) + try { + return GenericStack.fromItemStack(is); + } catch (Throwable ignored) { + return null; + } + } + } catch (Throwable ignored) { + } + return null; + } + + @SubscribeEvent + public static void onMouseButtonPre(ScreenEvent.MouseButtonPressed.Pre event) { + // 优先处理:Shift + 左键(拉取或下单) + if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) { + double mouseX = event.getMouseX(); + double mouseY = event.getMouseY(); + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); + if (hovered.isEmpty()) { + hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + } + if (hovered.isPresent()) { + // 若 JEI 作弊模式开启,则放行给 JEI 处理(Shift+左键=一组) + if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { + return; + } + ITypedIngredient typed = hovered.get(); + GenericStack stack = toGenericStack(typed); + if (stack != null) { + // 发送到服务端:若网络有库存则拉取一组到空槽,否则若可合成则打开下单界面 + ModNetwork.CHANNEL.sendToServer(new PullFromJeiOrCraftC2SPacket(stack)); + // 消费此次点击,避免 JEI/原版对左键的其它处理 + event.setCanceled(true); + return; + } + } + } + + // 中键:打开 AE 下单界面(保持原有功能) + if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) { + // 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取 + double mouseX = event.getMouseX(); + double mouseY = event.getMouseY(); + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); + if (hovered.isEmpty()) { + hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + } + if (hovered.isEmpty()) return; + + ITypedIngredient typed = hovered.get(); + // 若 JEI 作弊模式开启,则放行给 JEI 处理(中键=一组) + if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { + return; + } + GenericStack stack = toGenericStack(typed); + if (stack == null) return; + + // 发送到服务端,让其验证并打开 CraftAmountMenu + ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack)); + + // 消费此次点击,避免 JEI/原版对中键的其它处理 + event.setCanceled(true); + } + } + + @SubscribeEvent + public static void onKeyPressedPre(ScreenEvent.KeyPressed.Pre event) { + if (event.getKeyCode() != GLFW.GLFW_KEY_F) return; + + // 仅当鼠标确实悬停在 JEI 配料上时触发 + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + if (hovered.isEmpty()) return; + + ITypedIngredient typed = hovered.get(); + + // 通用获取显示名称(兼容物品/流体等) + String name = JeiRuntimeProxy.getTypedIngredientDisplayName(typed); + if (name == null || name.isEmpty()) return; + + // 写入 AE2 终端的搜索框 + var screen = Minecraft.getInstance().screen; + if (screen instanceof MEStorageScreen me) { + try { + MEStorageScreenAccessor acc = (MEStorageScreenAccessor) (Object) me; + acc.eap$getSearchField().setValue(name); + acc.eap$setSearchText(name); // 同步到 Repo 并刷新 + event.setCanceled(true); + return; + } catch (Throwable ignored) { + } + } + } +} diff --git a/src/main/java/com/extendedae_plus/client/ModConfigScreen.java b/src/main/java/com/extendedae_plus/client/ModConfigScreen.java new file mode 100644 index 0000000..b750c95 --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ModConfigScreen.java @@ -0,0 +1,161 @@ +package com.extendedae_plus.client; + +import com.extendedae_plus.config.ModConfigs; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.CycleButton; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +public class ModConfigScreen extends Screen { + private final Screen parent; + + // 输入控件 + private EditBox pageMultiplierBox; + private EditBox wirelessMaxRangeBox; + private CycleButton crossDimToggle; + private CycleButton providerRoundRobinToggle; + private EditBox smartScalingMaxMulBox; + private CycleButton showEncoderToggle; + private CycleButton patternTerminalShowSlotsToggle; + + public ModConfigScreen(Screen parent) { + super(Component.translatable("screen.extendedae_plus.title")); + this.parent = parent; + } + + @Override + protected void init() { + int centerX = this.width / 2; + int y = this.height / 6 + 24; // 起始高度,整体更上方 + int row = 0; + int rowHeight = 26; + int boxWidth = 150; + // 左右两列:左侧标签起点,右侧输入控件起点 + int leftX = centerX - 170; + int rightX = centerX + 20; + + // pageMultiplier: Int 1-64 + pageMultiplierBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.pageMultiplier")); + pageMultiplierBox.setValue(String.valueOf(ModConfigs.PAGE_MULTIPLIER.get())); + pageMultiplierBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 1) >= 1 && parseIntOrDefault(s, 64) <= 64); + this.addRenderableWidget(pageMultiplierBox); + row++; + + // wirelessMaxRange: Double 1-4096 + wirelessMaxRangeBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.wirelessMaxRange")); + wirelessMaxRangeBox.setValue(String.valueOf(ModConfigs.WIRELESS_MAX_RANGE.get())); + wirelessMaxRangeBox.setFilter(s -> s.isEmpty() || s.matches("\\d*(\\.\\d*)?")); + this.addRenderableWidget(wirelessMaxRangeBox); + row++; + + // cross dim toggle + crossDimToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get())); + row++; + + // provider round-robin toggle (smart doubling) + providerRoundRobinToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get())); + row++; + + // smartScalingMaxMultiplier: Int 0-1048576 (0 means unlimited) + smartScalingMaxMulBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.smartScalingMaxMultiplier")); + smartScalingMaxMulBox.setValue(String.valueOf(ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get())); + smartScalingMaxMulBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 0) >= 0 && parseIntOrDefault(s, 1048576) <= 1048576); + this.addRenderableWidget(smartScalingMaxMulBox); + row++; + + // show encoder pattern player toggle + showEncoderToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.get())); + row++; + + // pattern terminal show slots default toggle + patternTerminalShowSlotsToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT.get())); + row++; + + // 按钮:保存、返回 + int btnW = 100; + int gap = 8; + int buttonsY = y + row * rowHeight + 18; + this.addRenderableWidget(Button.builder(Component.translatable("gui.done"), b -> saveAndClose()) + .pos(centerX - btnW - gap/2, buttonsY) + .size(btnW, 20) + .build()); + this.addRenderableWidget(Button.builder(Component.translatable("gui.cancel"), b -> onClose()) + .pos(centerX + gap/2, buttonsY) + .size(btnW, 20) + .build()); + } + + private void saveAndClose() { + // 读取与校验 + int pageMul = clamp(parseIntOrDefault(pageMultiplierBox.getValue(), ModConfigs.PAGE_MULTIPLIER.get()), 1, 64); + double maxRange = clamp(parseDoubleOrDefault(wirelessMaxRangeBox.getValue(), ModConfigs.WIRELESS_MAX_RANGE.get()), 1.0, 4096.0); + boolean crossDim = crossDimToggle.getValue(); + boolean providerRoundRobin = providerRoundRobinToggle.getValue(); + int smartMaxMul = clamp(parseIntOrDefault(smartScalingMaxMulBox.getValue(), ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get()), 0, 1048576); + boolean showEncoder = showEncoderToggle.getValue(); + boolean patternShowSlots = patternTerminalShowSlotsToggle.getValue(); + + // 应用到 Forge 配置值 + ModConfigs.PAGE_MULTIPLIER.set(pageMul); + ModConfigs.WIRELESS_MAX_RANGE.set(maxRange); + ModConfigs.WIRELESS_CROSS_DIM_ENABLE.set(crossDim); + ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.set(providerRoundRobin); + ModConfigs.SMART_SCALING_MAX_MULTIPLIER.set(smartMaxMul); + ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.set(showEncoder); + ModConfigs.PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT.set(patternShowSlots); + + // Forge 会在合适的时机写回到配置文件;部分改动可能需要重启游戏或世界才完全生效 + onClose(); + } + + // Helper to create a boolean on/off CycleButton which shows localized on/off text + private CycleButton createToggle(int x, int y, int width, int height, boolean initial) { + CycleButton btn = CycleButton.onOffBuilder(initial) + .create(x, y, width, height, Component.empty(), (b, v) -> b.setMessage(Component.translatable(v ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off"))); + btn.setMessage(Component.translatable(initial ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off")); + return btn; + } + + @Override + public void onClose() { + Minecraft.getInstance().setScreen(parent); + } + + @Override + public void render(GuiGraphics g, int mouseX, int mouseY, float partialTick) { + this.renderBackground(g); + super.render(g, mouseX, mouseY, partialTick); + + int centerX = this.width / 2; + int y = this.height / 6 + 24; + int rowHeight = 26; + int labelColor = 0xFFFFFF; + int leftX = centerX - 170; // 标签左列位置 + + // 标题 + g.drawCenteredString(this.font, this.title, centerX, y - 28, 0xFFFFFF); + + // 每行标签 + g.drawString(this.font, Component.translatable("config.extendedae_plus.pageMultiplier_with_range"), leftX, y + 0 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessMaxRange_with_range"), leftX, y + 1 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessCrossDimEnable"), leftX, y + 2 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.providerRoundRobinEnable"), leftX, y + 3 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.smartScalingMaxMultiplier_with_range"), leftX, y + 4 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.showEncoderPatternPlayer"), leftX, y + 5 * rowHeight + 6, labelColor, false); + g.drawString(this.font, Component.translatable("config.extendedae_plus.patternTerminalShowSlotsDefault"), leftX, y + 6 * rowHeight + 6, labelColor, false); + } + + private static int parseIntOrDefault(String s, int def) { + try { return Integer.parseInt(s); } catch (Exception e) { return def; } + } + + private static double parseDoubleOrDefault(String s, double def) { + try { return Double.parseDouble(s); } catch (Exception e) { return def; } + } + + private static int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); } + private static double clamp(double v, double min, double max) { return Math.max(min, Math.min(max, v)); } +} diff --git a/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java b/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java new file mode 100644 index 0000000..967336f --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/render/crafting/EPlusCraftingCubeModelProvider.java @@ -0,0 +1,89 @@ +package com.extendedae_plus.client.render.crafting; + +import appeng.client.render.crafting.AbstractCraftingUnitModelProvider; +import appeng.client.render.crafting.LightBakedModel; +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.content.crafting.EPlusCraftingUnitType; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.client.ChunkRenderTypeSet; +import net.neoforged.neoforge.client.model.data.ModelData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +/** + * 形成态光照模型。 + */ +public class EPlusCraftingCubeModelProvider + extends AbstractCraftingUnitModelProvider { + + public static final ChunkRenderTypeSet CUTOUT = ChunkRenderTypeSet.of(RenderType.cutout()); + private static final List MATERIALS = new ArrayList<>(); + + //将环形边框与基础发光底图放在本模组命名空间 + protected static final Material RING_CORNER = texture(ExtendedAEPlus.MODID, "ring_corner"); + protected static final Material RING_SIDE_HOR = texture(ExtendedAEPlus.MODID, "ring_side_hor"); + protected static final Material RING_SIDE_VER = texture(ExtendedAEPlus.MODID, "ring_side_ver"); + protected static final Material LIGHT_BASE = texture(ExtendedAEPlus.MODID, "light_base"); + + // 亮面贴图(formed 时使用) + protected static final Material ACCELERATOR_4X_LIGHT = texture(ExtendedAEPlus.MODID, + "4x_accelerator_light"); + protected static final Material ACCELERATOR_16X_LIGHT = texture(ExtendedAEPlus.MODID, + "16x_accelerator_light"); + protected static final Material ACCELERATOR_64X_LIGHT = texture(ExtendedAEPlus.MODID, + "64x_accelerator_light"); + protected static final Material ACCELERATOR_256X_LIGHT = texture(ExtendedAEPlus.MODID, + "256x_accelerator_light"); + protected static final Material ACCELERATOR_1024X_LIGHT = texture(ExtendedAEPlus.MODID, + "1024x_accelerator_light"); + + public EPlusCraftingCubeModelProvider(EPlusCraftingUnitType type) { + super(type); + } + + @Override + public List getMaterials() { + return Collections.unmodifiableList(MATERIALS); + } + + @Override + public BakedModel getBakedModel(Function spriteGetter) { + TextureAtlasSprite ringCorner = spriteGetter.apply(RING_CORNER); + TextureAtlasSprite ringSideHor = spriteGetter.apply(RING_SIDE_HOR); + TextureAtlasSprite ringSideVer = spriteGetter.apply(RING_SIDE_VER); + + return new LightBakedModel(ringCorner, ringSideHor, ringSideVer, + spriteGetter.apply(LIGHT_BASE), this.getLightMaterial(spriteGetter)) { + public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { + return CUTOUT; + } + }; + } + + private TextureAtlasSprite getLightMaterial(Function textureGetter) { + return switch (this.type) { + case ACCELERATOR_4x -> textureGetter.apply(ACCELERATOR_4X_LIGHT); + case ACCELERATOR_16x -> textureGetter.apply(ACCELERATOR_16X_LIGHT); + case ACCELERATOR_64x -> textureGetter.apply(ACCELERATOR_64X_LIGHT); + case ACCELERATOR_256x -> textureGetter.apply(ACCELERATOR_256X_LIGHT); + case ACCELERATOR_1024x -> textureGetter.apply(ACCELERATOR_1024X_LIGHT); + }; + } + + private static Material texture(String namespace, String name) { + var mat = new Material(TextureAtlas.LOCATION_BLOCKS, + new ResourceLocation(namespace, "block/crafting/" + name)); + MATERIALS.add(mat); + return mat; + } +} 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/client/ui/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java new file mode 100644 index 0000000..e817f2a --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java @@ -0,0 +1,405 @@ +package com.extendedae_plus.client.ui; + +import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.util.*; + +/** + * 简单的供应器选择弹窗。 + * 展示若干个可点击的供应器条目,点击后发送带 providerId 的上传请求。 + */ +public class ProviderSelectScreen extends Screen { + private final Screen parent; + // 原始数据 + private final List ids; + private final List names; + private final List emptySlots; + + // 分组后的数据(同名合并) + private final List gIds = new ArrayList<>(); // 代表条目使用的 providerId:选择空位数最多的那个 + private final List gNames = new ArrayList<>(); // 分组名(供应器名称) + private final List gTotalSlots = new ArrayList<>(); // 该名称下供应器空位总和 + private final List gCount = new ArrayList<>(); // 该名称下供应器数量 + + // 过滤后的数据(由查询生成) + private final List fIds = new ArrayList<>(); + private final List fNames = new ArrayList<>(); + private final List fTotalSlots = new ArrayList<>(); + private final List fCount = new ArrayList<>(); + + // 搜索框 + private EditBox searchBox; + // 中文名输入框(用于添加映射) + private EditBox cnInput; + private String query = ""; + private boolean needsRefresh = false; + + private int page = 0; + private static final int PAGE_SIZE = 6; + + private final List