ExtendedAE_Plus/src/main/java/com/extendedae_plus/mixin/GuiExPatternProviderMixin.java
2025-08-10 22:54:23 +08:00

412 lines
19 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.extendedae_plus.mixin;
import appeng.client.gui.Icon;
import appeng.client.gui.implementations.PatternProviderScreen;
import appeng.client.gui.style.PaletteColor;
import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.VerticalButtonBar;
import appeng.menu.SlotSemantics;
import com.extendedae_plus.NewIcon;
import com.extendedae_plus.util.PatternProviderUIHelper;
import com.glodblock.github.extendedae.client.button.ActionEPPButton;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import com.glodblock.github.extendedae.container.ContainerExPatternProvider;
import com.extendedae_plus.network.UpdatePagePacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.Slot;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.reflect.Field;
import java.util.List;
@Mixin(GuiExPatternProvider.class)
public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<ContainerExPatternProvider> {
@Unique
ScreenStyle screenStyle;
@Unique
private VerticalButtonBar rightToolbar;
@Unique
private static final int SLOTS_PER_PAGE = 36; // 每页显示36个槽位
public GuiExPatternProviderMixin(ContainerExPatternProvider menu, Inventory playerInventory, Component title, ScreenStyle style) {
super(menu, playerInventory, title, style);
}
@Unique
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) {
super.render(guiGraphics, mouseX, mouseY, partialTicks);
int maxSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size();
// 只有当槽位数超过每页显示数量时才显示翻页信息
if (maxSlots > SLOTS_PER_PAGE) {
Font fontRenderer = Minecraft.getInstance().font;
// 获取当前页码
int currentPage = getCurrentPage();
int maxPage = getMaxPage();
// 获取ae通用界面样式
int color = screenStyle.getColor(PaletteColor.DEFAULT_TEXT_COLOR).toARGB();
// 调整页码显示位置:在"样板"文字的右边
guiGraphics.drawString(font, Component.literal("" + (currentPage + 1) + "/" + maxPage + ""),
leftPos + 8 + 50, topPos + 30, color, false);
}
}
@Unique
public void updateBeforeRender() {
super.updateBeforeRender();
try {
ContainerExPatternProvider menu1 = this.getMenu();
// 调用showPage方法
java.lang.reflect.Method showPageMethod = menu1.getClass().getMethod("showPage");
showPageMethod.invoke(menu1);
// 获取当前页码和最大页码
Field fieldPage = menu1.getClass().getDeclaredField("page");
fieldPage.setAccessible(true);
Integer page = (Integer) fieldPage.get(menu1);
Field fieldMaxPage = menu1.getClass().getDeclaredField("maxPage");
fieldMaxPage.setAccessible(true);
Integer maxPage = (Integer) fieldMaxPage.get(menu1);
// 更新按钮可见性 - 始终显示,支持循环翻页
if (nextPage != null && prevPage != null) {
this.nextPage.setVisibility(true);
this.prevPage.setVisibility(true);
}
if (x2Button != null) {
this.x2Button.setVisibility(true);
}
if (divideBy2Button != null) {
this.divideBy2Button.setVisibility(true);
}
// 调整槽位位置
this.adjustSlotPositions(page);
} catch (Exception e) {
// 忽略反射错误
}
}
@Unique
private void adjustSlotPositions(int currentPage) {
try {
List<Slot> slots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN);
int totalSlots = slots.size();
if (totalSlots <= SLOTS_PER_PAGE) {
return; // 不需要翻页
}
int slot_id = 0;
for (Slot s : slots) {
int page_id = slot_id / SLOTS_PER_PAGE;
if (page_id == currentPage) {
// 当前页的槽位需要调整位置
int slotInPage = slot_id % SLOTS_PER_PAGE;
int row = slotInPage / 9; // 0-3
int col = slotInPage % 9; // 0-8
// 计算目标位置始终在前4行
int x = 8 + col * 18;
int y = 42 + row * 18;
// 使用反射设置槽位位置,支持混淆环境
Field xField = null;
Field yField = null;
// 尝试不同的字段名(开发环境和生产环境可能不同)
String[] xFieldNames = {"x", "field_75262_c"};
String[] yFieldNames = {"y", "field_75263_d"};
for (String fieldName : xFieldNames) {
try {
xField = Slot.class.getDeclaredField(fieldName);
xField.setAccessible(true);
break;
} catch (NoSuchFieldException ignored) {}
}
for (String fieldName : yFieldNames) {
try {
yField = Slot.class.getDeclaredField(fieldName);
yField.setAccessible(true);
break;
} catch (NoSuchFieldException ignored) {}
}
if (xField != null && yField != null) {
xField.set(s, x);
yField.set(s, y);
}
}
++slot_id;
}
} catch (Exception e) {
// 忽略反射错误
}
}
@Unique
private int getCurrentPage() {
try {
ContainerExPatternProvider menu1 = this.getMenu();
Field fieldPage = menu1.getClass().getDeclaredField("page");
fieldPage.setAccessible(true);
return (Integer) fieldPage.get(menu1);
} catch (Exception e) {
return 0;
}
}
@Unique
private int getMaxPage() {
try {
ContainerExPatternProvider menu1 = this.getMenu();
Field fieldMaxPage = menu1.getClass().getDeclaredField("maxPage");
fieldMaxPage.setAccessible(true);
return (Integer) fieldMaxPage.get(menu1);
} catch (Exception e) {
return 1;
}
}
public ActionEPPButton nextPage;
public ActionEPPButton prevPage;
public ActionEPPButton x2Button;
public ActionEPPButton divideBy2Button;
@Inject(method = "<init>", at = @At("RETURN"), remap = false)
private void injectInit(ContainerExPatternProvider menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
this.screenStyle = style;
this.rightToolbar = new VerticalButtonBar();
// 只有当槽位数超过每页显示数量时才添加翻页按钮
int maxSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size();
if (maxSlots > SLOTS_PER_PAGE) {
// 前进后退按钮
this.prevPage = new ActionEPPButton((b) -> {
int currentPage = getCurrentPage();
int maxPage = getMaxPage();
// 循环翻页:第一页向前翻到最后一页
int newPage = (currentPage - 1 + maxPage) % maxPage;
try {
ContainerExPatternProvider menu1 = this.getMenu();
java.lang.reflect.Method setPageMethod = menu1.getClass().getMethod("setPage", int.class);
setPageMethod.invoke(menu1, newPage);
} catch (Exception e) {
// 忽略反射错误
}
}, Icon.ARROW_LEFT);
this.nextPage = new ActionEPPButton((b) -> {
int currentPage = getCurrentPage();
int maxPage = getMaxPage();
// 循环翻页:最后一页向后翻到第一页
int newPage = (currentPage + 1) % maxPage;
try {
ContainerExPatternProvider menu1 = this.getMenu();
java.lang.reflect.Method setPageMethod = menu1.getClass().getMethod("setPage", int.class);
setPageMethod.invoke(menu1, newPage);
} catch (Exception e) {
// 忽略反射错误
}
}, Icon.ARROW_RIGHT);
this.addToLeftToolbar(this.nextPage);
this.addToLeftToolbar(this.prevPage);
}
// x2 按钮 - 单机模式直接调用服务器端逻辑
this.x2Button = new ActionEPPButton((b) -> {
try {
// 单机模式:直接在逻辑服务器端执行样板倍增
net.minecraft.client.Minecraft minecraft = net.minecraft.client.Minecraft.getInstance();
if (minecraft.level != null && minecraft.player != null) {
// 获取逻辑服务器端的玩家实例
net.minecraft.server.level.ServerPlayer serverPlayer = minecraft.getSingleplayerServer()
.getPlayerList().getPlayer(minecraft.player.getUUID());
if (serverPlayer != null) {
// 在服务器端执行样板倍增逻辑
executePatternScalingOnServer(serverPlayer, "MULTIPLY", 2.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板倍增时发生错误:" + e.getMessage());
e.printStackTrace();
}
}, NewIcon.MULTIPLY2);
// /2 按钮 - 单机模式直接调用服务器端逻辑
this.divideBy2Button = new ActionEPPButton((b) -> {
try {
// 单机模式:直接在逻辑服务器端执行样板除法
net.minecraft.client.Minecraft minecraft = net.minecraft.client.Minecraft.getInstance();
if (minecraft.level != null && minecraft.player != null) {
// 获取逻辑服务器端的玩家实例
net.minecraft.server.level.ServerPlayer serverPlayer = minecraft.getSingleplayerServer()
.getPlayerList().getPlayer(minecraft.player.getUUID());
if (serverPlayer != null) {
// 在服务器端执行样板除法逻辑
executePatternScalingOnServer(serverPlayer, "DIVIDE", 2.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板除法时发生错误:" + e.getMessage());
e.printStackTrace();
}
}, NewIcon.DIVIDE2);
this.addToLeftToolbar(this.x2Button);
this.addToLeftToolbar(this.divideBy2Button);
}
/**
* 在服务器端执行样板缩放操作(单机模式)
*/
@Unique
private void executePatternScalingOnServer(net.minecraft.server.level.ServerPlayer serverPlayer, String scalingType, double scaleFactor) {
try {
// 将实际逻辑切换到服务端主线程执行,避免跨线程访问导致读取到空库存
serverPlayer.getServer().execute(() -> {
try {
// 直接基于容器槽位操作,完全绕开 PatternProviderLogic 及其内部字段
if (!(serverPlayer.containerMenu instanceof com.glodblock.github.extendedae.container.ContainerExPatternProvider exMenu)) {
System.out.println("ExtendedAE Plus: 当前容器不是 ExPatternProvider无法执行样板缩放");
return;
}
int scaled = 0;
int failed = 0;
int total = 0;
final int scale = (int) Math.round(scaleFactor);
final boolean div = !"MULTIPLY".equals(scalingType);
java.util.List<net.minecraft.world.inventory.Slot> slots = exMenu.getSlots(appeng.menu.SlotSemantics.ENCODED_PATTERN);
for (var slot : slots) {
var stack = slot.getItem();
if (stack.getItem() instanceof appeng.crafting.pattern.EncodedPatternItem patternItem) {
total++;
var detail = patternItem.decode(stack, serverPlayer.level(), false);
if (detail instanceof appeng.crafting.pattern.AEProcessingPattern process) {
var input = process.getSparseInputs();
var output = process.getOutputs();
// 检查是否可修改来源ExtendedAE ContainerPatternModifier.checkModify
if (checkModifyLikeExtendedAE(input, scale, div) && checkModifyLikeExtendedAE(output, scale, div)) {
var mulInput = new appeng.api.stacks.GenericStack[input.length];
var mulOutput = new appeng.api.stacks.GenericStack[output.length];
modifyStacksLikeExtendedAE(input, mulInput, scale, div);
modifyStacksLikeExtendedAE(output, mulOutput, scale, div);
var newPattern = appeng.api.crafting.PatternDetailsHelper.encodeProcessingPattern(mulInput, mulOutput);
if (slot instanceof appeng.menu.slot.AppEngSlot as) {
as.set(newPattern);
} else {
slot.set(newPattern);
}
scaled++;
} else {
failed++;
}
} else {
// 非处理样板:跳过
failed++;
}
}
}
// 构造结果并回显
String message;
if (scaled == 0) {
message = String.format(
" ExtendedAE Plus: 样板%s完成但未处理任何样板。共发现 %d 个样板,失败 %d 个(可能全为合成样板或数量不满足条件)",
div ? "除法" : "倍增", total, failed);
} else if (failed > 0) {
message = String.format("✅ ExtendedAE Plus: 样板%s完成处理了 %d 个,跳过 %d 个", div ? "除法" : "倍增", scaled, failed);
} else {
message = String.format("✅ ExtendedAE Plus: 样板%s成功处理了 %d 个", div ? "除法" : "倍增", scaled);
}
var minecraft = net.minecraft.client.Minecraft.getInstance();
if (minecraft.player != null) {
minecraft.player.displayClientMessage(net.minecraft.network.chat.Component.literal(message), true);
}
System.out.println("ExtendedAE Plus: " + message);
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 服务器端执行样板缩放时发生错误: " + e.getMessage());
e.printStackTrace();
}
});
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 调度到服务器主线程时发生错误: " + e.getMessage());
e.printStackTrace();
}
}
@Unique
private boolean checkModifyLikeExtendedAE(appeng.api.stacks.GenericStack[] stacks, int scale, boolean div) {
if (div) {
for (var stack : stacks) {
if (stack != null) {
if (stack.amount() % scale != 0) {
return false;
}
}
}
} else {
for (var stack : stacks) {
if (stack != null) {
long upper = 999999L * stack.what().getAmountPerUnit();
if (stack.amount() * scale > upper) {
return false;
}
}
}
}
return true;
}
@Unique
private void modifyStacksLikeExtendedAE(appeng.api.stacks.GenericStack[] stacks,
appeng.api.stacks.GenericStack[] des,
int scale,
boolean div) {
for (int i = 0; i < stacks.length; i++) {
if (stacks[i] != null) {
long amt = div ? stacks[i].amount() / scale : stacks[i].amount() * scale;
des[i] = new appeng.api.stacks.GenericStack(stacks[i].what(), amt);
}
}
}
}