[释放]修正研究节点未解锁时可以使用已有合成的漏洞;修复要素中继接口状态栏;删除源质发信器多余渲染。
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package thaumicenergistics.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
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 appeng.api.networking.crafting.ICraftingSubmitResult;
|
||||
import appeng.client.gui.me.crafting.CraftConfirmScreen;
|
||||
import appeng.client.gui.me.crafting.CraftErrorScreen;
|
||||
import appeng.menu.me.crafting.CraftConfirmMenu;
|
||||
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
|
||||
/**
|
||||
* 客户端:研究未解锁拦截的提示渲染。
|
||||
*
|
||||
* 服务端拦截合成提交后,通过 ResearchGateErrorPacket 发送研究键;
|
||||
* 本类在合成屏(确认屏/错误屏)之上绘制提示文字(最上层,不被 GUI 遮挡),
|
||||
* 并暴露 hasResearchGateError() 供 MixinCraftConfirmScreen 判断是否拦截 CPU 忙错误屏。
|
||||
*/
|
||||
@EventBusSubscriber(modid = ThaumicEnergistics.MODID, value = Dist.CLIENT)
|
||||
public final class ResearchGateOverlay {
|
||||
|
||||
private static String pendingResearch = null;
|
||||
|
||||
private ResearchGateOverlay() {}
|
||||
|
||||
/** 网络包处理:记录研究键(客户端线程)。 */
|
||||
public static void setPendingResearch(String research) {
|
||||
pendingResearch = research;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前菜单是否处于研究未解锁拦截状态。
|
||||
* 依据 CraftConfirmMenu.submitError 判断:错误结果存在且 errorCode 非空,
|
||||
* 说明本次提交被拒绝(研究拦截触发 CPU_BUSY)。
|
||||
*/
|
||||
public static boolean hasResearchGateError() {
|
||||
return pendingResearch != null;
|
||||
}
|
||||
|
||||
/** 清空研究拦截状态(玩家离开合成屏或成功重试时由外部调用)。 */
|
||||
public static void clearPendingResearch() {
|
||||
pendingResearch = null;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onScreenRenderPost(ScreenEvent.Render.Post event) {
|
||||
// 玩家已离开合成屏(不是确认屏/错误屏)→ 清除拦截状态
|
||||
if (!(event.getScreen() instanceof CraftConfirmScreen)
|
||||
&& !(event.getScreen() instanceof CraftErrorScreen)) {
|
||||
if (pendingResearch != null) {
|
||||
clearPendingResearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 无拦截状态不渲染
|
||||
if (pendingResearch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GuiGraphics gui = event.getGuiGraphics();
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int width = mc.getWindow().getGuiScaledWidth();
|
||||
|
||||
// 纯色无样式,保证与 MC 默认 GUI 字体一致
|
||||
Component msg = Component.literal("当前研究节点未解锁:" + pendingResearch);
|
||||
|
||||
int textWidth = mc.font.width(msg);
|
||||
int x = (width - textWidth) / 2;
|
||||
int y = 30;
|
||||
|
||||
drawWithShadow(gui, mc, msg, x, y, 0xFF5555, 0x000000);
|
||||
}
|
||||
|
||||
/** 以指定颜色绘制文字并叠 8 向同偏移描边,保证在全屏 GUI 之上可读。 */
|
||||
private static void drawWithShadow(GuiGraphics gui, Minecraft mc, Component msg, int x, int y,
|
||||
int textColor, int outlineColor) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
if (dx == 0 && dy == 0) {
|
||||
continue;
|
||||
}
|
||||
gui.drawString(mc.font, msg, x + dx, y + dy, outlineColor);
|
||||
}
|
||||
}
|
||||
gui.drawString(mc.font, msg, x, y, textColor);
|
||||
}
|
||||
}
|
||||
+20
@@ -4,8 +4,10 @@ import appeng.api.crafting.PatternDetailsHelper;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -13,11 +15,13 @@ import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||
import net.neoforged.neoforge.items.SlotItemHandler;
|
||||
import thaumcraft.api.ThaumcraftApiHelper;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.common.research.TCResearchManager;
|
||||
import thaumcraft.common.research.ThaumometerScanManager;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
|
||||
@@ -31,6 +35,10 @@ import java.util.List;
|
||||
|
||||
public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
||||
|
||||
/** 蒸馏编码统一要求的研究节点(要素蒸馏)。 */
|
||||
private static final String RESEARCH_DISTILESSENTIA = "DISTILESSENTIA";
|
||||
private static final String NBTKEY_RESEARCH = "research";
|
||||
|
||||
private static final int PLAYER_INV_Y = 150;
|
||||
private static final int HOTBAR_Y = 208;
|
||||
private static final int PLAYER_INV_X = 8;
|
||||
@@ -249,6 +257,13 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
||||
|
||||
public void onEncodePattern() {
|
||||
if (encoder == null) return;
|
||||
|
||||
// 研究限制:必须解锁"要素蒸馏"才能配置物品→源质蒸馏样板
|
||||
if (!TCResearchManager.has(playerInv.player, RESEARCH_DISTILESSENTIA)) {
|
||||
ThaumicEnergistics.LOG.info("[DistillationEncoder] 玩家未解锁研究 {},拒绝编码蒸馏样板", RESEARCH_DISTILESSENTIA);
|
||||
return;
|
||||
}
|
||||
|
||||
Aspect selected = getSelectedAspect();
|
||||
ItemStack source = getSourceItem();
|
||||
ItemStack blankSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS);
|
||||
@@ -288,6 +303,11 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
||||
List.of(output)
|
||||
);
|
||||
|
||||
// 在样板上标记研究键(供使用端按玩家校验)
|
||||
CompoundTag researchTag = new CompoundTag();
|
||||
researchTag.putString(NBTKEY_RESEARCH, RESEARCH_DISTILESSENTIA);
|
||||
encoded.set(DataComponents.CUSTOM_DATA, CustomData.of(researchTag));
|
||||
|
||||
encoder.inventory.setStackInSlot(TileDistillationPatternEncoder.SLOT_ENCODED_PATTERN, encoded);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import thaumcraft.api.aspects.AspectList;
|
||||
import thaumcraft.api.crafting.IArcaneRecipe;
|
||||
import thaumcraft.api.crafting.ShapedArcaneRecipe;
|
||||
import thaumcraft.api.crafting.ShapelessArcaneRecipe;
|
||||
import thaumcraft.common.research.TCResearchManager;
|
||||
import thaumicenergistics.common.container.slot.GhostSlot;
|
||||
import thaumicenergistics.common.integration.tc.ArcaneCraftingPattern;
|
||||
import thaumicenergistics.common.inventory.HandlerKnowledgeCore;
|
||||
@@ -220,6 +221,13 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
||||
return CoreSaveState.Disabled_InvalidRecipe;
|
||||
}
|
||||
|
||||
// 研究限制:配方有研究要求且玩家未解锁 → 禁止保存
|
||||
String research = activeRecipe.getResearch();
|
||||
if (research != null && !research.isEmpty()
|
||||
&& !TCResearchManager.has(playerInv.player, research)) {
|
||||
return CoreSaveState.Disabled_InvalidRecipe;
|
||||
}
|
||||
|
||||
boolean isNew = !kCoreHandler.hasPatternFor(result);
|
||||
if (isNew) {
|
||||
return kCoreHandler.hasRoomToStorePattern()
|
||||
@@ -364,6 +372,14 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
||||
private void savePattern() {
|
||||
if (activeRecipe == null) return;
|
||||
|
||||
// 研究限制:配方有研究要求时,操作者必须已解锁对应研究才能编码
|
||||
String research = activeRecipe.getResearch();
|
||||
if (research != null && !research.isEmpty()
|
||||
&& !TCResearchManager.has(playerInv.player, research)) {
|
||||
ThaumicEnergistics.LOG.info("[KnowledgeInscriber] 玩家未解锁研究 {},拒绝保存奥术样板", research);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack[] inputs = new ItemStack[CRAFTING_GRID_SIZE];
|
||||
for (int i = 0; i < CRAFTING_GRID_SIZE; i++) {
|
||||
ItemStack stack = craftingInventory.getStackInSlot(i);
|
||||
@@ -376,7 +392,7 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
||||
}
|
||||
ItemStack result = resultInventory.getStackInSlot(0).copy();
|
||||
|
||||
ArcaneCraftingPattern pattern = new ArcaneCraftingPattern(aspects, result, inputs);
|
||||
ArcaneCraftingPattern pattern = new ArcaneCraftingPattern(aspects, result, inputs, research);
|
||||
kCoreHandler.addPattern(pattern);
|
||||
updatePatternSlots();
|
||||
sendSaveState();
|
||||
|
||||
@@ -72,10 +72,6 @@ public class GuiEssentiaLevelEmitter extends UpgradeableScreen<ContainerEssentia
|
||||
guiGraphics.drawString(this.font, aspect.getName(),
|
||||
ASPECT_SLOT_X + 20, ASPECT_SLOT_Y + 4, 0xFFFFFF, false);
|
||||
}
|
||||
|
||||
guiGraphics.drawString(this.font,
|
||||
"Current: " + this.menu.currentLevel,
|
||||
8, 70, 0xAAAAAA, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ public class ArcaneCraftingPattern {
|
||||
private static final String NBTKEY_INGREDIENT_NUM = "input#";
|
||||
private static final String NBTKEY_RESULT = "output";
|
||||
private static final String NBTKEY_ASPECTS = "aspects";
|
||||
private static final String NBTKEY_RESEARCH = "research";
|
||||
private static final int GRID_SIZE = 9;
|
||||
|
||||
/** 全局 RegistryAccess,用于 NBT 读写 */
|
||||
@@ -27,10 +28,17 @@ public class ArcaneCraftingPattern {
|
||||
protected ItemStack[] ingredients = new ItemStack[GRID_SIZE];
|
||||
protected ItemStack result = ItemStack.EMPTY;
|
||||
protected boolean isValid;
|
||||
/** 对应 TC 奥术配方的研究键(用于使用端按玩家校验解锁)。可为空(老数据/无研究配方)。 */
|
||||
protected String research;
|
||||
|
||||
public ArcaneCraftingPattern(AspectList aspects, ItemStack result, ItemStack[] ingredients) {
|
||||
this(aspects, result, ingredients, null);
|
||||
}
|
||||
|
||||
public ArcaneCraftingPattern(AspectList aspects, ItemStack result, ItemStack[] ingredients, String research) {
|
||||
this.aspects = aspects.copy();
|
||||
this.result = result.copy();
|
||||
this.research = research;
|
||||
|
||||
boolean hasValidInput = false;
|
||||
for (int i = 0; i < GRID_SIZE && i < ingredients.length; i++) {
|
||||
@@ -72,6 +80,12 @@ public class ArcaneCraftingPattern {
|
||||
return aspects.getAmount(aspect);
|
||||
}
|
||||
|
||||
/** 对应 TC 奥术配方的研究键;无研究要求/老数据时可能为 null。 */
|
||||
@org.jetbrains.annotations.Nullable
|
||||
public String getResearch() {
|
||||
return research;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean canSubstituteFor(Object target, ItemStack input) {
|
||||
if (target instanceof ItemStack targetStack) {
|
||||
@@ -91,6 +105,10 @@ public class ArcaneCraftingPattern {
|
||||
this.ingredients = new ItemStack[GRID_SIZE];
|
||||
this.isValid = true;
|
||||
|
||||
if (data.contains(NBTKEY_RESEARCH)) {
|
||||
this.research = data.getString(NBTKEY_RESEARCH);
|
||||
}
|
||||
|
||||
if (data.contains(NBTKEY_ASPECTS)) {
|
||||
this.aspects.readFromNBT(data.getCompound(NBTKEY_ASPECTS));
|
||||
}
|
||||
@@ -133,6 +151,10 @@ public class ArcaneCraftingPattern {
|
||||
this.aspects.writeToNBT(aspectTag);
|
||||
data.put(NBTKEY_ASPECTS, aspectTag);
|
||||
|
||||
if (this.research != null && !this.research.isEmpty()) {
|
||||
data.putString(NBTKEY_RESEARCH, this.research);
|
||||
}
|
||||
|
||||
for (int i = 0; i < GRID_SIZE; i++) {
|
||||
if (this.ingredients[i] == null || this.ingredients[i].isEmpty()) continue;
|
||||
// 1.21.1: ItemStack.save(Provider) 返回新的 Tag,不会写入传入的 CompoundTag。
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package thaumicenergistics.common.integration.tc;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import thaumicenergistics.common.tiles.TileArcaneAssembler;
|
||||
|
||||
/**
|
||||
* 从 AE2 {@link IPatternDetails} 中提取 TC 研究键的辅助类。
|
||||
*
|
||||
* 两种样板携带研究键的方式:
|
||||
* - 奥术装配器样板 {@link TileArcaneAssembler.ArcaneAssemblerPattern}:直接持 research 字段
|
||||
* - 蒸馏处理样板(AE2 标准 processing pattern):通过样板上 DataComponents.CUSTOM_DATA 的 research 字段标记
|
||||
*/
|
||||
public final class PatternResearchHelper {
|
||||
|
||||
public static final String NBTKEY_RESEARCH = "research";
|
||||
|
||||
private PatternResearchHelper() {}
|
||||
|
||||
/**
|
||||
* 提取样板对应的 TC 研究键;无研究要求或未知样板时返回 null(表示不限制)。
|
||||
*/
|
||||
@Nullable
|
||||
public static String getResearch(IPatternDetails pattern) {
|
||||
if (pattern instanceof TileArcaneAssembler.ArcaneAssemblerPattern arcanePattern) {
|
||||
return arcanePattern.getResearch();
|
||||
}
|
||||
|
||||
// 蒸馏处理样板:从样板上 CUSTOM_DATA 读 research 标记
|
||||
AEItemKey definition = pattern.getDefinition();
|
||||
if (definition != null) {
|
||||
ItemStack patternStack = definition.toStack();
|
||||
CustomData customData = patternStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData != null) {
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (tag.contains(NBTKEY_RESEARCH)) {
|
||||
String research = tag.getString(NBTKEY_RESEARCH);
|
||||
return research.isEmpty() ? null : research;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.client.ResearchGateOverlay;
|
||||
|
||||
/**
|
||||
* 服务端→客户端 网络包:告知玩家"研究未解锁"拦截发生,并携带具体研究键。
|
||||
*
|
||||
* 服务端在 submitJob 拦截(MixinCraftingService)时发送到对应玩家;
|
||||
* 客户端收到后由 {@link ResearchGateOverlay} 在最上层渲染提示。
|
||||
*/
|
||||
public record ResearchGateErrorPacket(String research) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<ResearchGateErrorPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("research_gate_error"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ResearchGateErrorPacket> STREAM_CODEC = StreamCodec.of(
|
||||
(buf, pkt) -> buf.writeUtf(pkt.research),
|
||||
buf -> new ResearchGateErrorPacket(buf.readUtf())
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端处理:记录最近一次研究未解锁拦截的研究键,供渲染层读取。
|
||||
*/
|
||||
public void handleOnClient(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
// 无论当前在什么界面,记录研究键
|
||||
ResearchGateOverlay.setPendingResearch(this.research);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -49,5 +49,12 @@ public final class ThENetworking {
|
||||
CellWorkbenchSyncPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnClient(ctx)
|
||||
);
|
||||
|
||||
// ResearchGateErrorPacket — S2C(服务端→客户端,研究未解锁拦截提示,携带研究键)
|
||||
registrar.playToClient(
|
||||
ResearchGateErrorPacket.TYPE,
|
||||
ResearchGateErrorPacket.STREAM_CODEC,
|
||||
(pkt, ctx) -> pkt.handleOnClient(ctx)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,13 +164,14 @@ public class VisInterfacePart extends P2PTunnelPart<VisInterfacePart> implements
|
||||
|
||||
/**
|
||||
* 找到面对中继的隧道端:优先 P2P 输入端,其次任一输出端,最后自身。
|
||||
* 注意:内部使用 super.getOutputs() 而非 getOutputs(),避免覆写导致递归。
|
||||
*/
|
||||
private VisInterfacePart findRelayEnd() {
|
||||
VisInterfacePart input = getInput();
|
||||
if (input != null && input.getRelay() != null) {
|
||||
return input;
|
||||
}
|
||||
for (VisInterfacePart output : getOutputs()) {
|
||||
for (VisInterfacePart output : super.getOutputs()) {
|
||||
if (output.getRelay() != null) {
|
||||
return output;
|
||||
}
|
||||
@@ -181,6 +182,25 @@ public class VisInterfacePart extends P2PTunnelPart<VisInterfacePart> implements
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 覆写输出端列表:半 P2P 场景下(装配器直接按频率查输入,无标准 P2P 输出端),
|
||||
* 若存在有效的中继端(findRelayEnd 非空,含自身面对中继的情况),
|
||||
* 将其视为已链接,使 AE2 P2P 状态显示(Jade/TOP)不再显示"Unlinked"。
|
||||
*/
|
||||
@Override
|
||||
public List<VisInterfacePart> getOutputs() {
|
||||
List<VisInterfacePart> outputs = super.getOutputs();
|
||||
if (!outputs.isEmpty()) {
|
||||
return outputs;
|
||||
}
|
||||
VisInterfacePart relayEnd = findRelayEnd();
|
||||
if (relayEnd != null) {
|
||||
// 含自身面对中继的情况:返回 relayEnd(可能是 this),让 P2P 状态显示已链接
|
||||
return relayEnd == this ? List.of(this) : List.of(relayEnd);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
private int drawFromRelay(VisRelayBlockEntity relay, ResourceLocation aspectId, int centivis) {
|
||||
if (relay == null) {
|
||||
return 0;
|
||||
|
||||
@@ -483,7 +483,8 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
}
|
||||
}
|
||||
|
||||
return new ArcaneAssemblerPattern(AEItemKey.of(result), null, aspects, result, inputs);
|
||||
return new ArcaneAssemblerPattern(AEItemKey.of(result), null, aspects, result, inputs,
|
||||
acp.getResearch());
|
||||
}
|
||||
|
||||
private boolean beginCrafting(ArcaneAssemblerPattern arcanePattern) {
|
||||
@@ -751,14 +752,22 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
private final AspectList aspects;
|
||||
private final ItemStack resultStack;
|
||||
private final IInput[] inputs;
|
||||
private final String research;
|
||||
|
||||
public ArcaneAssemblerPattern(AEItemKey definition, IArcaneRecipe recipe,
|
||||
AspectList aspects, ItemStack resultStack,
|
||||
List<GenericStack> inputStacks) {
|
||||
this(definition, recipe, aspects, resultStack, inputStacks, null);
|
||||
}
|
||||
|
||||
public ArcaneAssemblerPattern(AEItemKey definition, IArcaneRecipe recipe,
|
||||
AspectList aspects, ItemStack resultStack,
|
||||
List<GenericStack> inputStacks, String research) {
|
||||
this.definition = definition;
|
||||
this.recipe = recipe;
|
||||
this.aspects = aspects;
|
||||
this.resultStack = resultStack;
|
||||
this.research = research;
|
||||
this.inputs = new IInput[inputStacks.size()];
|
||||
for (int i = 0; i < inputStacks.size(); i++) {
|
||||
GenericStack stack = inputStacks.get(i);
|
||||
@@ -820,5 +829,8 @@ public class TileArcaneAssembler extends ThETileBase
|
||||
public IArcaneRecipe getRecipe() { return recipe; }
|
||||
public AspectList getAspects() { return aspects; }
|
||||
public ItemStack getResultStack() { return resultStack; }
|
||||
/** 对应 TC 奥术配方的研究键;无研究要求/老数据时可能为 null。 */
|
||||
@Nullable
|
||||
public String getResearch() { return research; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package thaumicenergistics.mixins.ae2;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import thaumicenergistics.client.ResearchGateOverlay;
|
||||
|
||||
/**
|
||||
* 拦截 CraftConfirmScreen 打开 CPU 忙错误屏。
|
||||
*
|
||||
* 研究未解锁拦截会触发 AE2 的 CPU_BUSY 错误屏;本 Mixin 在错误屏被 switchToScreen 之前
|
||||
* (updateBeforeRender HEAD)检测到研究拦截(ResearchGateOverlay 已有网络包写入的研究键),
|
||||
* 取消原渲染逻辑,避免打开误导性的"CPU 忙"窗口。
|
||||
* 提示文字由 ResearchGateOverlay 在无 GUI 遮挡时绘制。
|
||||
*/
|
||||
@Mixin(targets = "appeng.client.gui.me.crafting.CraftConfirmScreen", remap = false)
|
||||
public abstract class MixinCraftConfirmScreen {
|
||||
|
||||
@Inject(method = "updateBeforeRender()V", at = @At("HEAD"), cancellable = true, remap = false)
|
||||
private void thaumicenergistics$blockCpuBusyScreen(CallbackInfo ci) {
|
||||
// 网络包已写入研究键 = 本次失败是研究拦截,而非真实 CPU 忙碌
|
||||
// 使用 submitError 状态而非时间戳:只要错误未被玩家清除就持续拦截,避免过期后弹窗
|
||||
if (ResearchGateOverlay.hasResearchGateError()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package thaumicenergistics.mixins.ae2;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.crafting.CraftingSubmitErrorCode;
|
||||
import appeng.api.networking.crafting.ICraftingCPU;
|
||||
import appeng.api.networking.crafting.ICraftingPlan;
|
||||
import appeng.api.networking.crafting.ICraftingRequester;
|
||||
import appeng.api.networking.crafting.ICraftingSubmitResult;
|
||||
import appeng.api.networking.security.IActionSource;
|
||||
import appeng.crafting.execution.CraftingSubmitResult;
|
||||
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import thaumcraft.common.research.TCResearchManager;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.integration.tc.PatternResearchHelper;
|
||||
|
||||
/**
|
||||
* 使用端研究限制:玩家从 AE 终端发起合成请求时(submitJob),
|
||||
* 若请求涉及尚未解锁对应 TC 研究的样板(奥术/蒸馏),则拒绝该请求。
|
||||
*
|
||||
* 机器自动合成(IActionSource 无玩家)不拦截。
|
||||
*/
|
||||
@Mixin(targets = "appeng.me.service.CraftingService", remap = false)
|
||||
public abstract class MixinCraftingService {
|
||||
|
||||
@Inject(method = "submitJob", at = @At("HEAD"), cancellable = true, remap = false)
|
||||
private void thaumicenergistics$checkResearchBeforeSubmit(
|
||||
ICraftingPlan job,
|
||||
ICraftingRequester requestingMachine,
|
||||
ICraftingCPU target,
|
||||
boolean prioritizePower,
|
||||
IActionSource src,
|
||||
CallbackInfoReturnable<ICraftingSubmitResult> cir) {
|
||||
// 仅玩家发起的请求需要研究校验;机器自动合成放行
|
||||
if (src == null || src.player().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player player = src.player().get();
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<IPatternDetails, Long> entry : job.patternTimes().entrySet()) {
|
||||
IPatternDetails pattern = entry.getKey();
|
||||
String research = PatternResearchHelper.getResearch(pattern);
|
||||
if (research == null || research.isEmpty()) {
|
||||
continue; // 无研究要求
|
||||
}
|
||||
if (!TCResearchManager.has(player, research)) {
|
||||
ThaumicEnergistics.LOG.info(
|
||||
"[AE2ResearchGate] 玩家 {} 未解锁研究 {},拒绝合成请求 (pattern={})",
|
||||
player.getName().getString(), research, pattern.getDefinition());
|
||||
// 通过自定义网络包发送研究键到客户端(AE2 errorDetail 不跨网络传输)
|
||||
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
|
||||
net.neoforged.neoforge.network.PacketDistributor.sendToPlayer(
|
||||
serverPlayer,
|
||||
new thaumicenergistics.common.network.ResearchGateErrorPacket(research));
|
||||
}
|
||||
// 拒绝提交(CPU_BUSY 触发 AE2 错误屏;具体提示由客户端网络包渲染)
|
||||
cir.setReturnValue(CraftingSubmitResult.simpleError(CraftingSubmitErrorCode.CPU_BUSY));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user