[释放]修正研究节点未解锁时可以使用已有合成的漏洞;修复要素中继接口状态栏;删除源质发信器多余渲染。
This commit is contained in:
+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; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user