diff --git a/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java index f6a8415..d2465d9 100644 --- a/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java +++ b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java @@ -1,109 +1,228 @@ package com.extendedae_plus.client.screen; +import appeng.client.gui.Icon; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.menu.NetworkPatternControllerMenu; import com.extendedae_plus.network.provider.GlobalToggleProviderModesC2SPacket; +import com.extendedae_plus.network.provider.SetGlobalScalingLimitC2SPacket; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; +import org.jetbrains.annotations.NotNull; public class GlobalProviderModesScreen extends AbstractContainerScreen { private static final Component CUSTOM_TITLE = Component.translatable("block.extendedae_plus.network_pattern_controller"); + private static final ResourceLocation BACKGROUND = new ResourceLocation("ae2", "textures/guis/background.png"); + + private EditBox inputField; + private static final int INPUT_WIDTH = 50; + private static final int INPUT_HEIGHT = 14; + + // 布局常量 + private static final int IMAGE_WIDTH = 210; + private static final int IMAGE_HEIGHT = 165; + private static final int BTN_W = 62; + private static final int BTN_H = 18; + private static final int BTN_SPACING = 6; + private static final int START_Y = 25; + private static final int LABEL_INPUT_SPACING = 18; + public GlobalProviderModesScreen(NetworkPatternControllerMenu menu, Inventory inv, Component title) { super(menu, inv, title); - this.imageWidth = 240; - this.imageHeight = 140; + this.imageWidth = IMAGE_WIDTH; + this.imageHeight = IMAGE_HEIGHT; } @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 -> + int centerX = this.leftPos + this.imageWidth / 2; + int row1Y = this.topPos + START_Y; + int row2Y = row1Y + BTN_H + BTN_SPACING; + int row3Y = row2Y + BTN_H + BTN_SPACING; + + int totalWidth3 = BTN_W * 3 + BTN_SPACING * 2; + int row1X = centerX - totalWidth3 / 2; + + int totalWidth2 = BTN_W * 2 + BTN_SPACING; + int row2X = centerX - totalWidth2 / 2; + + addRenderableWidget(new AEStyleButton(row1X, row1Y, BTN_W, BTN_H, + 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 -> + addRenderableWidget(new AEStyleButton(row1X + BTN_W + BTN_SPACING, row1Y, BTN_W, BTN_H, + 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 -> + addRenderableWidget(new AEStyleButton(row1X + (BTN_W + BTN_SPACING) * 2, row1Y, BTN_W, BTN_H, + 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 -> + addRenderableWidget(new AEStyleButton(row2X, row2Y, BTN_W, BTN_H, + 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 -> + addRenderableWidget(new AEStyleButton(row2X + BTN_W + BTN_SPACING, row2Y, BTN_W, BTN_H, + 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()); + )))); + + // 输入框区域 - 整体居中布局 + int inputRowY = row3Y + LABEL_INPUT_SPACING; + int labelWidth = this.font.width(Component.translatable("gui.extendedae_plus.global.supplier_doubling_limit")); + int totalWidth = labelWidth + 8 + INPUT_WIDTH + 6 + 16; // 标签 + 间距 + 输入框 + 间距 + 按钮 + int startX = centerX - totalWidth / 2; + int labelX = startX; + int inputX = startX + labelWidth + 8; + int confirmX = inputX + INPUT_WIDTH + 6; + + inputField = createInputField(inputX, inputRowY + 1); + this.addRenderableWidget(inputField); + + addRenderableWidget(new AEConfirmButton(confirmX, inputRowY, + Component.translatable("gui.extendedae_plus.global.confirm_tooltip"), b -> { + String value = inputField.getValue(); + // 数据校验:解析并发送有效数值 + try { + String sValue = (value == null || value.isBlank()) ? "0" : value.replaceFirst("^0+(?=.)", ""); + int limit = Integer.parseInt(sValue); + ModNetwork.CHANNEL.sendToServer(new SetGlobalScalingLimitC2SPacket(limit, this.menu.getBlockEntityPos())); + } catch (NumberFormatException ignored) { + // 输入值无效,重置为0 + inputField.setValue("0"); + } + })); + } + + private EditBox createInputField(int x, int y) { + EditBox input = new EditBox(this.font, x, y, INPUT_WIDTH, INPUT_HEIGHT, Component.empty()); + input.setMaxLength(6); + input.setBordered(true); + input.setValue("0"); + input.setTextColor(0xFFFFFF); + // 添加数据校验响应器 + input.setResponder(s -> { + try { + String sValue = (s == null || s.isBlank()) ? "0" : s.replaceFirst("^0+(?=.)", ""); + if (!sValue.equals(s)) { + input.setValue(sValue); + } + Integer.parseInt(sValue); // 验证是否为有效整数 + } catch (Throwable ignored) { + // 输入无效,重置为0 + input.setValue("0"); + } + }); + return input; } @Override protected void renderBg(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, 0x80FFFFFF); - gfx.fill(panelLeft, panelTop, panelLeft + 1, panelBottom, 0x80FFFFFF); - gfx.fill(panelRight - 1, panelTop, panelRight, panelBottom, 0x80FFFFFF); + // 将256x256背景图压缩/拉伸到界面尺寸 + gfx.blit(BACKGROUND, this.leftPos, this.topPos, this.imageWidth, this.imageHeight, 0, 0, 256, 256, 256, 256); } @Override - public void render(GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) { - this.renderBackground(gfx); + public void render(@NotNull GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) { super.render(gfx, mouseX, mouseY, partialTicks); - gfx.drawString(this.font, CUSTOM_TITLE, this.leftPos + 10, this.topPos + 8, 0xFFFFFF, false); + + int centerX = this.leftPos + this.imageWidth / 2; + int row1Y = this.topPos + START_Y; + int row2Y = row1Y + BTN_H + BTN_SPACING; + int row3Y = row2Y + BTN_H + BTN_SPACING; + int inputRowY = row3Y + LABEL_INPUT_SPACING; + + // 计算居中位置并绘制标签 + int labelWidth = this.font.width(Component.translatable("gui.extendedae_plus.global.supplier_doubling_limit")); + int totalWidth = labelWidth + 8 + INPUT_WIDTH + 6 + 16; + int startX = centerX - totalWidth / 2; + int labelX = startX; + gfx.drawString(this.font, Component.translatable("gui.extendedae_plus.global.supplier_doubling_limit"), labelX, inputRowY + 4, 0x000000, false); } @Override protected void renderLabels(GuiGraphics gfx, int mouseX, int mouseY) { - // 不绘制默认的玩家物品栏标题(例如“物品栏”),避免与自定义面板重叠 - // 标题已在 render() 中手动绘制 + gfx.drawString(this.font, CUSTOM_TITLE, 8, 6, 0x404040, false); + } + + private static class AEStyleButton extends Button { + private static final int TEXT_COLOR = 0xFFFFFF; + private static final int BTN_BG = 0xFF3A3A3A; + private static final int BTN_BG_HOVER = 0xFF4A4A4A; + private static final int BTN_BORDER_LIGHT = 0xFFAAAAAA; + private static final int BTN_BORDER_DARK = 0xFF555555; + + public AEStyleButton(int x, int y, int width, int height, Component message, OnPress onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION); + } + + @Override + public void renderWidget(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partial) { + if (this.visible) { + int bgColor = isHovered() && this.active ? BTN_BG_HOVER : BTN_BG; + + // 填充背景 + guiGraphics.fill(getX(), getY(), getX() + width, getY() + height, bgColor); + guiGraphics.fill(getX(), getY(), getX() + width, getY() + 1, BTN_BORDER_LIGHT); + guiGraphics.fill(getX(), getY(), getX() + 1, getY() + height, BTN_BORDER_LIGHT); + guiGraphics.fill(getX() + width - 1, getY(), getX() + width, getY() + height, BTN_BORDER_DARK); + guiGraphics.fill(getX(), getY() + height - 1, getX() + width, getY() + height, BTN_BORDER_DARK); + renderString(guiGraphics, Minecraft.getInstance().font, TEXT_COLOR); + } + } + } + + private static class AEConfirmButton extends Button { + private final Component tooltip; + + public AEConfirmButton(int x, int y, Component tooltip, OnPress onPress) { + super(x, y, 16, 16, Component.empty(), onPress, DEFAULT_NARRATION); + this.tooltip = tooltip; + } + + @Override + public void renderWidget(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partial) { + if (this.visible) { + Icon icon = Icon.VALID; + Icon.TOOLBAR_BUTTON_BACKGROUND.getBlitter().dest(getX(), getY()).blit(guiGraphics); + icon.getBlitter().dest(getX(), getY()).blit(guiGraphics); + + // 绘制 Tooltip + if (isHovered()) { + guiGraphics.renderTooltip(Minecraft.getInstance().font, tooltip, mouseX, mouseY); + } + } + } } } diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index 5dae0a4..56aa5f6 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -131,6 +131,12 @@ public final class ModNetwork { .consumerNetworkThread(GlobalToggleProviderModesC2SPacket::handle) .add(); + CHANNEL.messageBuilder(SetGlobalScalingLimitC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(SetGlobalScalingLimitC2SPacket::encode) + .decoder(SetGlobalScalingLimitC2SPacket::decode) + .consumerNetworkThread(SetGlobalScalingLimitC2SPacket::handle) + .add(); + CHANNEL.messageBuilder(ToggleEntityTickerC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) .encoder(ToggleEntityTickerC2SPacket::encode) .decoder(ToggleEntityTickerC2SPacket::decode) diff --git a/src/main/java/com/extendedae_plus/network/provider/SetGlobalScalingLimitC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/SetGlobalScalingLimitC2SPacket.java new file mode 100644 index 0000000..3ae2414 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/provider/SetGlobalScalingLimitC2SPacket.java @@ -0,0 +1,132 @@ +package com.extendedae_plus.network.provider; + +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.smartDoubling.ISmartDoublingHolder; +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.HashSet; +import java.util.Set; +import java.util.function.Supplier; + +/** + * C2S: 设置网络中所有样板供应器的翻倍限制值 + */ +public class SetGlobalScalingLimitC2SPacket { + private final int limit; + private final BlockPos controllerPos; + + public SetGlobalScalingLimitC2SPacket(int limit, BlockPos controllerPos) { + this.limit = limit; + this.controllerPos = controllerPos; + } + + public static void encode(SetGlobalScalingLimitC2SPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.limit); + buf.writeBlockPos(msg.controllerPos); + } + + public static SetGlobalScalingLimitC2SPacket decode(FriendlyByteBuf buf) { + int limit = buf.readInt(); + BlockPos pos = buf.readBlockPos(); + return new SetGlobalScalingLimitC2SPacket(limit, pos); + } + + public static void handle(SetGlobalScalingLimitC2SPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + ServerPlayer player = ctx.getSender(); + if (player == null) return; + + 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.limit); + player.displayClientMessage(Component.translatable("extendedae_plus.chat.pattern_provider.global_scaling_limit_applied", affected, msg.limit), true); + }); + ctx.setPacketHandled(true); + } + + private static int applyToAllProviders(IGrid grid, int limit) { + 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 + 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) {} + + // 兼容 ExtendedAE + 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, limit)) 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; + } + 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, int limit) { + if (logic instanceof ISmartDoublingHolder holder) { + holder.eap$setProviderSmartDoublingLimit(limit); + logic.saveChanges(); + return true; + } + return false; + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index c24cd62..e016f8b 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -8,6 +8,9 @@ "gui.extendedae_plus.global.toggle_smart_doubling": "Toggle Smart Doubling", "gui.extendedae_plus.global.all_on": "All On", "gui.extendedae_plus.global.all_off": "All Off", + "gui.extendedae_plus.global.supplier_doubling_limit": "Supplier Doubling Limit:", + "gui.extendedae_plus.global.confirm_tooltip": "Confirm doubling limit value", + "extendedae_plus.chat.pattern_provider.global_scaling_limit_applied": "Set scaling limit to %2$s for %1$s providers", "gui.extendedae.pattern_page": "Page %s/%s", "itemGroup.extendedae_plus.main": "ExtendedAE Plus", 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 7ede5bf..f97f3b3 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -8,6 +8,9 @@ "gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍", "gui.extendedae_plus.global.all_on": "全部开启", "gui.extendedae_plus.global.all_off": "全部关闭", + "gui.extendedae_plus.global.supplier_doubling_limit": "供应器翻倍限制设置:", + "gui.extendedae_plus.global.confirm_tooltip": "确认设置翻倍限制数值", + "extendedae_plus.chat.pattern_provider.global_scaling_limit_applied": "已为 %s 个供应器设置翻倍限制为 %s", "gui.extendedae.pattern_page": "第%s页/%s页", "itemGroup.extendedae_plus.main": "扩展AE Plus",