修复改用网络发包,兼容多人游戏

This commit is contained in:
GaLi 2025-08-11 11:44:11 +08:00
parent 44e7d356fc
commit 16e929470b
6 changed files with 230 additions and 198 deletions

View File

@ -29,7 +29,7 @@ public class ExtendedAEPlus {
* 通用初始化设置
*/
private void commonSetup(final FMLCommonSetupEvent event) {
// 注册网络处理器
NetworkHandler.registerPackets();
// 注册网络处理器确保在注册窗口关闭前完成
event.enqueueWork(NetworkHandler::initialize);
}
}

View File

@ -5,7 +5,13 @@ import appeng.menu.SlotSemantics;
import appeng.menu.guisync.GuiSync;
import appeng.menu.implementations.PatternProviderMenu;
import appeng.menu.slot.AppEngSlot;
import appeng.crafting.pattern.EncodedPatternItem;
import appeng.crafting.pattern.AEProcessingPattern;
import appeng.api.stacks.GenericStack;
import appeng.api.crafting.PatternDetailsHelper;
import com.glodblock.github.extendedae.container.ContainerExPatternProvider;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import com.glodblock.github.glodium.network.packet.sync.Paras;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
@ -14,12 +20,15 @@ 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 org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
@Mixin(value = ContainerExPatternProvider.class, priority = 3000)
public abstract class ContainerExPatternProviderMixin extends PatternProviderMenu {
public abstract class ContainerExPatternProviderMixin extends PatternProviderMenu implements IActionHolder {
@GuiSync(11451)
@Unique
@ -31,6 +40,9 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
@Unique
private static final int SLOTS_PER_PAGE = 36; // 每页显示36个槽位
@Unique
private final Map<String, Consumer<Paras>> actions = createHolder();
public ContainerExPatternProviderMixin(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host) {
super(menuType, id, playerInventory, host);
}
@ -64,10 +76,20 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
}
}
@Inject(method = "<init>", at = @At("TAIL"), remap = false)
@Inject(method = "<init>", at = @At("TAIL"))
public void init(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
int maxSlots = this.getSlots(SlotSemantics.ENCODED_PATTERN).size();
this.maxPage = (maxSlots + SLOTS_PER_PAGE - 1) / SLOTS_PER_PAGE;
// 注册通用动作 CGenericPacket 分发
this.actions.put("multiply2", p -> { System.out.println("[EAE+][Server] multiply2"); modifyPatterns(2, false); });
this.actions.put("divide2", p -> { System.out.println("[EAE+][Server] divide2"); modifyPatterns(2, true); });
this.actions.put("multiply5", p -> { System.out.println("[EAE+][Server] multiply5"); modifyPatterns(5, false); });
this.actions.put("divide5", p -> { System.out.println("[EAE+][Server] divide5"); modifyPatterns(5, true); });
this.actions.put("multiply10",p -> { System.out.println("[EAE+][Server] multiply10");modifyPatterns(10, false);});
this.actions.put("divide10", p -> { System.out.println("[EAE+][Server] divide10"); modifyPatterns(10, true); });
System.out.println("[EAE+][Server] ContainerExPatternProvider actions registered: " + this.actions.keySet());
}
@Unique
@ -79,4 +101,72 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
public void setPage(int page) {
this.page = page;
}
}
@Unique
private void modifyPatterns(int scale, boolean div) {
if (scale <= 0) return;
for (var slot : this.getSlots(SlotSemantics.ENCODED_PATTERN)) {
var stack = slot.getItem();
if (stack.getItem() instanceof EncodedPatternItem pattern) {
var detail = pattern.decode(stack, this.getPlayer().level(), false);
if (detail instanceof AEProcessingPattern process) {
var input = process.getSparseInputs();
var output = process.getOutputs();
if (checkModify(input, scale, div) && checkModify(output, scale, div)) {
var mulInput = new GenericStack[input.length];
var mulOutput = new GenericStack[output.length];
modifyStacks(input, mulInput, scale, div);
modifyStacks(output, mulOutput, scale, div);
var newPattern = PatternDetailsHelper.encodeProcessingPattern(mulInput, mulOutput);
slot.set(newPattern);
}
}
}
}
}
@Unique
private boolean checkModify(GenericStack[] stacks, int scale, boolean div) {
if (stacks == null) return false;
if (div) {
for (var stack : stacks) {
if (stack != null) {
if (stack.amount() % scale != 0) {
return false;
}
}
}
return true;
} 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 modifyStacks(GenericStack[] src, GenericStack[] dst, int scale, boolean div) {
for (int i = 0; i < src.length; i++) {
var stack = src[i];
if (stack != null) {
long amt = stack.amount();
long newAmt = div ? (amt / scale) : (amt * scale);
dst[i] = new GenericStack(stack.what(), newAmt);
} else {
dst[i] = null;
}
}
}
@NotNull
@Override
public Map<String, Consumer<Paras>> getActionMap() {
return this.actions;
}
}

View File

@ -1,15 +1,25 @@
package com.extendedae_plus.mixin;
import appeng.menu.guisync.GuiSync;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import appeng.api.util.IConfigurableObject;
import com.glodblock.github.extendedae.container.ContainerExPatternTerminal;
import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
import com.glodblock.github.glodium.network.packet.sync.Paras;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
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.util.Map;
import java.util.function.Consumer;
import net.minecraft.world.entity.player.Player;
@Mixin(ContainerExPatternTerminal.class)
public abstract class ContainerExPatternTerminalMixin {
public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
@GuiSync(11452)
@Unique
@ -29,4 +39,34 @@ public abstract class ContainerExPatternTerminalMixin {
public void toggleHidePatternSlots() {
this.hidePatternSlots = !this.hidePatternSlots;
}
}
@Unique
private final Map<String, Consumer<Paras>> actions = createHolder();
@Unique
private Player epp$player;
@Inject(method = "<init>", at = @At("TAIL"))
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, IConfigurableObject host, CallbackInfo ci) {
this.epp$player = playerInventory.player;
// 注册上传动作参数顺序必须与客户端 CGenericPacket 保持一致
this.actions.put("upload", p -> {
try {
int playerSlotIndex = p.get(0);
long providerId = p.get(1);
var sp = (ServerPlayer) this.epp$player;
System.out.println("[EAE+][Server] upload: slot=" + playerSlotIndex + ", provider=" + providerId);
ExtendedAEPatternUploadUtil.uploadPatternToProvider(sp, playerSlotIndex, providerId);
} catch (Throwable t) {
t.printStackTrace();
}
});
System.out.println("[EAE+][Server] ExPatternTerminal actions registered: " + this.actions.keySet());
}
@NotNull
@Override
public Map<String, Consumer<Paras>> getActionMap() {
return this.actions;
}
}

View File

@ -9,9 +9,10 @@ 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.network.EPPNetworkHandler;
import com.glodblock.github.glodium.network.packet.CGenericPacket;
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;
@ -271,194 +272,86 @@ public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<Co
public ActionEPPButton divideBy5Button;
public ActionEPPButton x10Button;
public ActionEPPButton divideBy10Button;
@Inject(method = "<init>", at = @At("RETURN"), remap = false)
// 在构造器返回后初始化按钮与翻页控制
@Inject(method = "<init>", at = @At("RETURN"))
private void injectInit(ContainerExPatternProvider menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
this.screenStyle = style;
// 打印当前菜单类型确认是否为扩展容器
try {
var m = this.getMenu();
System.out.println("[EAE+][Client] Screen menu class = " + (m == null ? "null" : m.getClass().getName()));
} catch (Throwable ignored) {}
// 只有当槽位数超过每页显示数量时才添加翻页按钮
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);
// 翻页按钮仅在需要时显示
int totalSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size();
if (totalSlots > 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 ignored) {}
}, 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.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 ignored) {}
}, Icon.ARROW_RIGHT);
this.addToLeftToolbar(this.nextPage);
this.addToLeftToolbar(this.prevPage);
}
// x2 按钮 - 单机模式直接调用服务器端逻辑
// 倍增/除法按钮通过 ExtendedAE 的通用包派发
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();
}
System.out.println("[EAE+][Client] click multiply2");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("multiply2"));
}, NewIcon.MULTIPLY2);
this.x2Button.setVisibility(true);
// /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();
}
System.out.println("[EAE+][Client] click divide2");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("divide2"));
}, NewIcon.DIVIDE2);
this.divideBy2Button.setVisibility(true);
// 使用原版渲染注册确保在屏幕中绘制
this.addRenderableWidget(this.divideBy2Button);
this.addRenderableWidget(this.x2Button);
// x10 按钮 - 单机模式直接调用服务器端逻辑
this.x10Button = 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", 10.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板x10倍增时发生错误" + e.getMessage());
e.printStackTrace();
}
System.out.println("[EAE+][Client] click multiply10");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("multiply10"));
}, NewIcon.MULTIPLY10);
this.x10Button.setVisibility(true);
// x5 按钮 - 单机模式直接调用服务器端逻辑
this.x5Button = 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", 5.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板x5倍增时发生错误" + e.getMessage());
e.printStackTrace();
}
}, NewIcon.MULTIPLY5);
this.x5Button.setVisibility(true);
// /10 按钮 - 单机模式直接调用服务器端逻辑
this.divideBy10Button = 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", 10.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板/10时发生错误" + e.getMessage());
e.printStackTrace();
}
System.out.println("[EAE+][Client] click divide10");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("divide10"));
}, NewIcon.DIVIDE10);
this.divideBy10Button.setVisibility(true);
// /5 按钮 - 单机模式直接调用服务器端逻辑
this.divideBy5Button = 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", 5.0);
} else {
System.out.println("ExtendedAE Plus: 无法获取服务器端玩家实例");
}
} else {
System.out.println("ExtendedAE Plus: 单机服务器未启动或玩家为null");
}
} catch (Exception e) {
System.out.println("ExtendedAE Plus: 执行样板/5时发生错误" + e.getMessage());
e.printStackTrace();
}
System.out.println("[EAE+][Client] click divide5");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("divide5"));
}, NewIcon.DIVIDE5);
this.divideBy5Button.setVisibility(true);
// 注册新增按钮
this.x5Button = new ActionEPPButton((b) -> {
System.out.println("[EAE+][Client] click multiply5");
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("multiply5"));
}, NewIcon.MULTIPLY5);
this.x5Button.setVisibility(true);
// 注册可渲染按钮
this.addRenderableWidget(this.divideBy2Button);
this.addRenderableWidget(this.x2Button);
this.addRenderableWidget(this.divideBy5Button);
this.addRenderableWidget(this.x5Button);
this.addRenderableWidget(this.divideBy10Button);

View File

@ -5,6 +5,8 @@ import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.IconButton;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import com.glodblock.github.extendedae.network.EPPNetworkHandler;
import com.glodblock.github.glodium.network.packet.CGenericPacket;
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
import com.glodblock.github.extendedae.client.gui.GuiWirelessExPAT;
import com.glodblock.github.extendedae.container.ContainerExPatternTerminal;
@ -109,28 +111,10 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<ContainerEx
ItemStack itemToUpload = this.minecraft.player.getInventory().getItem(playerSlotIndex);
if (!itemToUpload.isEmpty() && PatternDetailsHelper.isEncodedPattern(itemToUpload)) {
// 取消上传过程中的左下角提示
// 在单机游戏中直接在客户端线程中执行服务器端逻辑
// 因为单机游戏的客户端和服务器运行在同一个进程中
this.minecraft.execute(() -> {
// 获取服务器端的玩家实例
if (this.minecraft.getSingleplayerServer() != null) {
var serverPlayer = this.minecraft.getSingleplayerServer().getPlayerList()
.getPlayer(this.minecraft.player.getUUID());
if (serverPlayer != null) {
// 直接调用服务器端上传逻辑
boolean success = ExtendedAEPatternUploadUtil.uploadPatternToProvider(
serverPlayer,
playerSlotIndex,
currentlychooicepatterprovider
);
// 取消上传完成后的左下角提示
}
}
});
// 通过 ExtendedAE 内置网络系统发送通用动作到服务端
// 动作: "upload"参数: 槽位索引(int)供应器ID(long)
System.out.println("[EAE+][Client] send upload: slot=" + playerSlotIndex + ", provider=" + currentlychooicepatterprovider);
EPPNetworkHandler.INSTANCE.sendToServer(new CGenericPacket("upload", playerSlotIndex, currentlychooicepatterprovider));
} else {
this.minecraft.player.displayClientMessage(

View File

@ -14,19 +14,40 @@ import net.minecraftforge.network.simple.SimpleChannel;
public class NetworkHandler {
private static final String PROTOCOL_VERSION = "1";
public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel(
new ResourceLocation("extendedae_plus", "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
);
private static SimpleChannel INSTANCE;
private static int packetId = 0;
private static boolean registered = false;
/**
* 在合法的注册阶段创建通道可重复调用幂等
*/
private static void initChannel() {
if (INSTANCE == null) {
INSTANCE = NetworkRegistry.newSimpleChannel(
new ResourceLocation("extendedae_plus", "main"),
() -> PROTOCOL_VERSION,
PROTOCOL_VERSION::equals,
PROTOCOL_VERSION::equals
);
}
}
/**
* Mod 早期调用确保在注册窗口关闭前完成通道与包注册
*/
public static void initialize() {
if (!registered) {
initChannel();
registerPackets();
}
}
/**
* 注册所有网络包
*/
public static void registerPackets() {
if (registered) return;
initChannel();
// 样板上传请求包客户端 -> 服务器
INSTANCE.messageBuilder(PatternUploadPacket.class, packetId++, NetworkDirection.PLAY_TO_SERVER)
.decoder(PatternUploadPacket::decode)
@ -54,12 +75,15 @@ public class NetworkHandler {
.encoder(PatternScalingResultPacket::encode)
.consumerMainThread(PatternScalingResultPacket::handle)
.add();
registered = true;
}
/**
* 发送包到服务器
*/
public static void sendToServer(Object packet) {
// 如果出现 null说明初始化阶段未被调用避免在锁定后临时创建通道
if (INSTANCE == null) throw new IllegalStateException("Network channel not initialized");
INSTANCE.sendToServer(packet);
}
@ -67,6 +91,7 @@ public class NetworkHandler {
* 发送包到指定客户端
*/
public static void sendToClient(Object packet, ServerPlayer player) {
if (INSTANCE == null) throw new IllegalStateException("Network channel not initialized");
INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), packet);
}
}