提交
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.ContainerEssentiaCellWorkbench;
|
||||
|
||||
/**
|
||||
* 客户端→服务端 网络包:源质元件工作台分区操作。
|
||||
* 操作类型见 ACTION_* 常量;aspectA/aspectB 仅在对应操作使用(其余传 EMPTY)。
|
||||
*/
|
||||
public record CellWorkbenchC2SPacket(
|
||||
int containerId,
|
||||
int action,
|
||||
ResourceLocation aspectA,
|
||||
ResourceLocation aspectB
|
||||
) implements CustomPacketPayload {
|
||||
|
||||
public static final int ACTION_REQUEST = 0;
|
||||
public static final int ACTION_ADD = 1;
|
||||
public static final int ACTION_REMOVE = 2;
|
||||
public static final int ACTION_REPLACE = 3;
|
||||
public static final int ACTION_CLEAR = 4;
|
||||
public static final int ACTION_PARTITION_TO_CONTENTS = 5;
|
||||
|
||||
private static final ResourceLocation EMPTY = ResourceLocation.withDefaultNamespace("empty");
|
||||
|
||||
public static final Type<CellWorkbenchC2SPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("cell_workbench_c2s"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, CellWorkbenchC2SPacket> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, CellWorkbenchC2SPacket::containerId,
|
||||
ByteBufCodecs.VAR_INT, CellWorkbenchC2SPacket::action,
|
||||
ResourceLocation.STREAM_CODEC, CellWorkbenchC2SPacket::aspectA,
|
||||
ResourceLocation.STREAM_CODEC, CellWorkbenchC2SPacket::aspectB,
|
||||
CellWorkbenchC2SPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
// ===== 客户端静态发送(从当前打开的工作台菜单自动取 containerId) =====
|
||||
|
||||
private static void send(int action, ResourceLocation a, ResourceLocation b) {
|
||||
var player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
PacketDistributor.sendToServer(new CellWorkbenchC2SPacket(player.containerMenu.containerId, action, a, b));
|
||||
}
|
||||
|
||||
public static void sendRequestPartitions() {
|
||||
send(ACTION_REQUEST, EMPTY, EMPTY);
|
||||
}
|
||||
|
||||
public static void sendAddAspect(ResourceLocation aspect) {
|
||||
send(ACTION_ADD, aspect, EMPTY);
|
||||
}
|
||||
|
||||
public static void sendRemoveAspect(ResourceLocation aspect) {
|
||||
send(ACTION_REMOVE, aspect, EMPTY);
|
||||
}
|
||||
|
||||
public static void sendReplaceAspect(ResourceLocation from, ResourceLocation to) {
|
||||
send(ACTION_REPLACE, from, to);
|
||||
}
|
||||
|
||||
public static void sendClear() {
|
||||
send(ACTION_CLEAR, EMPTY, EMPTY);
|
||||
}
|
||||
|
||||
public static void sendPartitionToContents() {
|
||||
send(ACTION_PARTITION_TO_CONTENTS, EMPTY, EMPTY);
|
||||
}
|
||||
|
||||
// ===== 服务端处理 =====
|
||||
|
||||
public void handleOnServer(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (ctx.player().containerMenu.containerId == this.containerId
|
||||
&& ctx.player().containerMenu instanceof ContainerEssentiaCellWorkbench menu) {
|
||||
boolean changed;
|
||||
switch (this.action) {
|
||||
case ACTION_REQUEST -> {
|
||||
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer sp) {
|
||||
CellWorkbenchSyncPacket.sendToClient(sp, menu.getPartitionAspects());
|
||||
}
|
||||
return;
|
||||
}
|
||||
case ACTION_ADD -> changed = menu.addAspectToPartition(this.aspectA);
|
||||
case ACTION_REMOVE -> changed = menu.removeAspectFromPartition(this.aspectA);
|
||||
case ACTION_REPLACE -> changed = menu.replaceAspectInPartition(this.aspectA, this.aspectB);
|
||||
case ACTION_CLEAR -> {
|
||||
menu.clearPartitioning();
|
||||
changed = true;
|
||||
}
|
||||
case ACTION_PARTITION_TO_CONTENTS -> {
|
||||
menu.partitionToContents();
|
||||
changed = true;
|
||||
}
|
||||
default -> changed = false;
|
||||
}
|
||||
if (changed) {
|
||||
if (ctx.player() instanceof net.minecraft.server.level.ServerPlayer sp) {
|
||||
CellWorkbenchSyncPacket.sendToClient(sp, menu.getPartitionAspects());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.GuiEssentiaCellWorkbench;
|
||||
|
||||
/**
|
||||
* 服务端→客户端 网络包:同步源质元件工作台的分区列表。
|
||||
*
|
||||
* 客户端收到后更新 {@link GuiEssentiaCellWorkbench#updatePartitions},刷新 63 个分区格。
|
||||
*/
|
||||
public record CellWorkbenchSyncPacket(
|
||||
List<ResourceLocation> partitions
|
||||
) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<CellWorkbenchSyncPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("cell_workbench_sync"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, CellWorkbenchSyncPacket> STREAM_CODEC = StreamCodec.of(
|
||||
(buf, pkt) -> {
|
||||
buf.writeVarInt(pkt.partitions.size());
|
||||
for (ResourceLocation rl : pkt.partitions) {
|
||||
ResourceLocation.STREAM_CODEC.encode(buf, rl);
|
||||
}
|
||||
},
|
||||
buf -> {
|
||||
int n = buf.readVarInt();
|
||||
List<ResourceLocation> list = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
list.add(ResourceLocation.STREAM_CODEC.decode(buf));
|
||||
}
|
||||
return new CellWorkbenchSyncPacket(list);
|
||||
}
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
/** 服务端发送分区列表到指定玩家。 */
|
||||
public static void sendToClient(ServerPlayer player, List<ResourceLocation> partitions) {
|
||||
PacketDistributor.sendToPlayer(player, new CellWorkbenchSyncPacket(partitions));
|
||||
}
|
||||
|
||||
/** 客户端处理:更新打开的工作台 GUI 分区格。 */
|
||||
public void handleOnClient(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (Minecraft.getInstance().screen instanceof GuiEssentiaCellWorkbench gui) {
|
||||
gui.updatePartitions(this.partitions);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.api.distmarker.OnlyIn;
|
||||
import thaumicenergistics.common.entities.BackpackSkins;
|
||||
import thaumcraft.common.entities.golem.ThaumcraftGolemEntity;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* 客户端侧缓存:存储每个傀儡的背包连接状态和皮肤。
|
||||
*
|
||||
* 由 {@link GolemBackpackSyncPacket#handleOnClient} 写入,
|
||||
* 由 {@link thaumicenergistics.client.render.layer.GolemBackpackLayer} 读取。
|
||||
*
|
||||
* 使用 WeakHashMap 以避免实体被 GC 后内存泄漏。
|
||||
*
|
||||
* 对应 1.7.10 的 {@code WirelessClientData},但简化为静态缓存而非挂在 handlerData 上。
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public final class GolemBackpackClientData {
|
||||
|
||||
private GolemBackpackClientData() {}
|
||||
|
||||
/** 傀儡 → 连接状态 (0=无背包, 1=在范围内, 2=超出范围) */
|
||||
private static final Map<ThaumcraftGolemEntity, Integer> connectionStatusMap = new WeakHashMap<>();
|
||||
|
||||
/** 傀儡 → 皮肤 */
|
||||
private static final Map<ThaumcraftGolemEntity, BackpackSkins> skinMap = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* 更新傀儡的背包数据(由网络包处理器调用)。
|
||||
*/
|
||||
public static void update(ThaumcraftGolemEntity golem, int connectionStatus, int skinOrdinal) {
|
||||
if (connectionStatus == GolemBackpackSyncPacket.STATUS_NO_BACKPACK) {
|
||||
connectionStatusMap.remove(golem);
|
||||
skinMap.remove(golem);
|
||||
} else {
|
||||
connectionStatusMap.put(golem, connectionStatus);
|
||||
skinMap.put(golem, BackpackSkins.fromOrdinal(skinOrdinal));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取傀儡的连接状态。
|
||||
*
|
||||
* @return 0=无背包, 1=在范围内, 2=超出范围
|
||||
*/
|
||||
public static int getConnectionStatus(ThaumcraftGolemEntity golem) {
|
||||
return connectionStatusMap.getOrDefault(golem, GolemBackpackSyncPacket.STATUS_NO_BACKPACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 傀儡是否装备了背包且在 AE 网络范围内。
|
||||
*/
|
||||
public static boolean isInRange(ThaumcraftGolemEntity golem) {
|
||||
return getConnectionStatus(golem) == GolemBackpackSyncPacket.STATUS_IN_RANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 傀儡是否装备了背包(无论是否在范围内)。
|
||||
*/
|
||||
public static boolean hasBackpack(ThaumcraftGolemEntity golem) {
|
||||
return getConnectionStatus(golem) != GolemBackpackSyncPacket.STATUS_NO_BACKPACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取傀儡的背包皮肤。
|
||||
*/
|
||||
public static BackpackSkins getSkin(ThaumcraftGolemEntity golem) {
|
||||
return skinMap.getOrDefault(golem, BackpackSkins.Thaumium);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.entities.BackpackSkins;
|
||||
import thaumcraft.common.entities.golem.ThaumcraftGolemEntity;
|
||||
|
||||
/**
|
||||
* 服务端→客户端 网络包:同步傀儡背包的连接状态和皮肤。
|
||||
*
|
||||
* 替代 1.7.10 的 DataWatcher 同步机制(SYNCFLAG_HAS_WIFI_IN_RANGE / OUT_OF_RANGE / NO_WIFI)。
|
||||
*
|
||||
* 服务端每 18 tick 检测一次背包连接状态,变化时发送此包到所有追踪该傀儡的客户端。
|
||||
* 客户端收到后更新 {@link GolemBackpackClientData},供渲染层读取。
|
||||
*/
|
||||
public record GolemBackpackSyncPacket(
|
||||
/** 傀儡实体 ID */
|
||||
int entityId,
|
||||
/** 连接状态:0=无背包, 1=在范围内, 2=超出范围 */
|
||||
int connectionStatus,
|
||||
/** 皮肤序号(对应 {@link BackpackSkins} ordinal) */
|
||||
int skinOrdinal
|
||||
) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<GolemBackpackSyncPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("golem_backpack_sync"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, GolemBackpackSyncPacket> STREAM_CODEC = StreamCodec.of(
|
||||
(buf, pkt) -> {
|
||||
buf.writeVarInt(pkt.entityId);
|
||||
buf.writeVarInt(pkt.connectionStatus);
|
||||
buf.writeVarInt(pkt.skinOrdinal);
|
||||
},
|
||||
buf -> new GolemBackpackSyncPacket(
|
||||
buf.readVarInt(),
|
||||
buf.readVarInt(),
|
||||
buf.readVarInt()
|
||||
)
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
/** 连接状态常量 */
|
||||
public static final int STATUS_NO_BACKPACK = 0;
|
||||
public static final int STATUS_IN_RANGE = 1;
|
||||
public static final int STATUS_OUT_OF_RANGE = 2;
|
||||
|
||||
/**
|
||||
* 客户端处理:更新该傀儡的客户端背包数据缓存。
|
||||
*/
|
||||
public void handleOnClient(IPayloadContext ctx) {
|
||||
// 在主线程上执行
|
||||
ctx.enqueueWork(() -> {
|
||||
Entity entity = Minecraft.getInstance().level.getEntity(this.entityId);
|
||||
if (entity instanceof ThaumcraftGolemEntity golem) {
|
||||
GolemBackpackClientData.update(golem, this.connectionStatus, this.skinOrdinal);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.ContainerKnowledgeInscriber;
|
||||
|
||||
/**
|
||||
* 客户端→服务端 网络包:同步知识记录仪合成网格中某个幽灵槽的内容。
|
||||
* 幽灵槽不走标准 Slot 同步流程(因为不消耗物品,只是摆个样子),
|
||||
* 服务端需要知道客户端摆了什么才能计算奥术配方、更新保存按钮状态。
|
||||
*/
|
||||
public record KnowledgeInscriberGhostSlotPacket(
|
||||
/** Container 的 containerId,用来在服务端找到对应菜单实例 */
|
||||
int containerId,
|
||||
/** 幽灵槽在合成网格中的索引(0-8) */
|
||||
int slotIndex,
|
||||
/** 该槽要设置的物品 */
|
||||
ItemStack stack
|
||||
) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<KnowledgeInscriberGhostSlotPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("ki_ghost_slot"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, KnowledgeInscriberGhostSlotPacket> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
net.minecraft.network.codec.ByteBufCodecs.VAR_INT, KnowledgeInscriberGhostSlotPacket::containerId,
|
||||
net.minecraft.network.codec.ByteBufCodecs.VAR_INT, KnowledgeInscriberGhostSlotPacket::slotIndex,
|
||||
ItemStack.OPTIONAL_STREAM_CODEC, KnowledgeInscriberGhostSlotPacket::stack,
|
||||
KnowledgeInscriberGhostSlotPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务端处理:找到对应菜单,更新幽灵槽,重新计算配方。
|
||||
*/
|
||||
public void handleOnServer(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
if (ctx.player().containerMenu.containerId == this.containerId
|
||||
&& ctx.player().containerMenu instanceof ContainerKnowledgeInscriber menu) {
|
||||
menu.setGhostSlotOnServer(this.slotIndex, this.stack);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package thaumicenergistics.common.network;
|
||||
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
|
||||
public final class ThENetworking {
|
||||
|
||||
private ThENetworking() {}
|
||||
|
||||
public static void init(IEventBus modBus) {
|
||||
modBus.addListener(ThENetworking::registerPayloads);
|
||||
}
|
||||
|
||||
private static void registerPayloads(RegisterPayloadHandlersEvent event) {
|
||||
PayloadRegistrar registrar = event.registrar("thaumicenergistics").versioned("1");
|
||||
|
||||
// GolemBackpackSyncPacket — S2C(服务端→客户端,同步背包连接状态和皮肤)
|
||||
registrar.playToClient(
|
||||
GolemBackpackSyncPacket.TYPE,
|
||||
GolemBackpackSyncPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> ctx.enqueueWork(() -> pkt.handleOnClient(ctx))
|
||||
);
|
||||
|
||||
// KnowledgeInscriberGhostSlotPacket — C2S(客户端→服务端,同步知识记录仪幽灵槽)
|
||||
registrar.playToServer(
|
||||
KnowledgeInscriberGhostSlotPacket.TYPE,
|
||||
KnowledgeInscriberGhostSlotPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||
);
|
||||
|
||||
// CellWorkbenchC2SPacket — C2S(客户端→服务端,源质元件工作台分区操作)
|
||||
registrar.playToServer(
|
||||
CellWorkbenchC2SPacket.TYPE,
|
||||
CellWorkbenchC2SPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||
);
|
||||
|
||||
// CellWorkbenchSyncPacket — S2C(服务端→客户端,同步工作台分区列表)
|
||||
registrar.playToClient(
|
||||
CellWorkbenchSyncPacket.TYPE,
|
||||
CellWorkbenchSyncPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnClient(ctx)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user