From 83143de9d3edb095764b1432d83b5a6c65b9a01d Mon Sep 17 00:00:00 2001 From: 1820982382 Date: Tue, 4 Aug 2026 16:34:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../container/ContainerArcaneAssembler.java | 62 ++++++++++++- .../ContainerArcaneCraftingTerminal.java | 7 ++ .../ContainerDistillationPatternEncoder.java | 50 ++++++---- .../ContainerKnowledgeInscriber.java | 68 +++++++++----- .../common/container/GuiArcaneAssembler.java | 60 +++++++----- .../container/GuiArcaneCraftingTerminal.java | 2 +- .../common/features/RecipeRegistration.java | 4 +- .../integration/tc/PseudoResearchItem.java | 2 +- .../common/items/ItemEssentiaCell.java | 14 +-- .../items/ItemGolemWirelessBackpack.java | 2 +- .../items/PartItemEssentiaExportBus.java | 2 +- .../items/PartItemEssentiaImportBus.java | 2 +- .../items/PartItemEssentiaStorageBus.java | 2 +- ...DistillationEncoderSetSourceC2SPacket.java | 45 +++++++++ .../common/network/ThENetworking.java | 7 ++ .../common/storage/TransportPermissions.java | 4 +- .../common/tiles/TileArcaneAssembler.java | 21 +++-- .../common/tiles/TileEssentiaProvider.java | 6 +- .../thaumicenergistics/init/ModBlocks.java | 11 ++- .../jei/ArcaneCraftingTransferInfo.java | 4 +- ...illationEncoderGhostIngredientHandler.java | 55 +++++++++++ ...nowledgeInscriberArcaneRecipeTransfer.java | 93 +++++++++++++++++++ ...wledgeInscriberGhostIngredientHandler.java | 65 +++++++++++++ .../jei/KnowledgeInscriberTransferInfo.java | 62 +++++++++++++ .../integration/jei/ThEJeiPlugin.java | 13 +++ .../ae2/screens/arcane_crafting_terminal.json | 2 +- .../models/block/infusion_provider.json | 7 +- 27 files changed, 568 insertions(+), 104 deletions(-) create mode 100644 src/main/java/thaumicenergistics/common/network/DistillationEncoderSetSourceC2SPacket.java create mode 100644 src/main/java/thaumicenergistics/integration/jei/DistillationEncoderGhostIngredientHandler.java create mode 100644 src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberArcaneRecipeTransfer.java create mode 100644 src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberGhostIngredientHandler.java create mode 100644 src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberTransferInfo.java diff --git a/src/main/java/thaumicenergistics/common/container/ContainerArcaneAssembler.java b/src/main/java/thaumicenergistics/common/container/ContainerArcaneAssembler.java index 2f577ed..b03f961 100644 --- a/src/main/java/thaumicenergistics/common/container/ContainerArcaneAssembler.java +++ b/src/main/java/thaumicenergistics/common/container/ContainerArcaneAssembler.java @@ -5,12 +5,15 @@ import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerData; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.items.ItemStackHandler; import net.neoforged.neoforge.items.SlotItemHandler; import thaumcraft.api.IVisDiscountGear; import thaumcraft.api.IWarpingGear; +import thaumcraft.api.aspects.Aspect; +import thaumicenergistics.ThaumicEnergistics; import thaumicenergistics.common.tiles.TileArcaneAssembler; import thaumicenergistics.init.ModMenuTypes; @@ -81,6 +84,47 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu { public final TileArcaneAssembler assembler; + /** 可视数据同步:服务端读 tile,客户端存同步值(GUI 进度条/源质条不依赖 BE 网络同步,靠容器每 tick 同步) */ + public final ContainerData visData; + + /** 索引约定 */ + public static final int DATA_AIR = 0, DATA_WATER = 1, DATA_FIRE = 2, DATA_ORDER = 3, + DATA_ENTROPY = 4, DATA_EARTH = 5, DATA_CRAFTING = 6, DATA_CRAFT_TICK = 7, DATA_TICKS_PER_CRAFT = 8; + public static final int DATA_COUNT = 9; + + /** 服务端:每次 get 都从 tile 读实时值(broadcastChanges 每 tick 调用) */ + private static class TileVisData implements ContainerData { + private final TileArcaneAssembler tile; + TileVisData(TileArcaneAssembler tile) { this.tile = tile; } + /** 节流:每 4 单位才变一次(broadcastChanges 只发变化的值 → 发包频率降 4 倍,进度条仍流畅) */ + private static int throttle(int v) { return (v / 4) * 4; } + @Override public int get(int index) { + if (tile == null || tile.getStoredVis() == null) return 0; + return switch (index) { + case DATA_AIR -> throttle(tile.getStoredVis().getAmount(Aspect.AIR)); + case DATA_WATER -> throttle(tile.getStoredVis().getAmount(Aspect.WATER)); + case DATA_FIRE -> throttle(tile.getStoredVis().getAmount(Aspect.FIRE)); + case DATA_ORDER -> throttle(tile.getStoredVis().getAmount(Aspect.ORDER)); + case DATA_ENTROPY -> throttle(tile.getStoredVis().getAmount(Aspect.ENTROPY)); + case DATA_EARTH -> throttle(tile.getStoredVis().getAmount(Aspect.EARTH)); + case DATA_CRAFTING -> tile.isCrafting() ? 1 : 0; + case DATA_CRAFT_TICK -> throttle(tile.getCraftTickCounter()); + case DATA_TICKS_PER_CRAFT -> tile.getTicksPerCraft(); + default -> 0; + }; + } + @Override public void set(int index, int value) { /* 服务端只读 */ } + @Override public int getCount() { return DATA_COUNT; } + } + + /** 客户端:保存服务端同步来的值 */ + private static class ClientVisData implements ContainerData { + private final int[] values = new int[DATA_COUNT]; + @Override public int get(int index) { return index >= 0 && index < DATA_COUNT ? values[index] : 0; } + @Override public void set(int index, int value) { if (index >= 0 && index < DATA_COUNT) values[index] = value; } + @Override public int getCount() { return DATA_COUNT; } + } + /** 服务端构造(由 Block#getMenuProvider 调用,不带 tile) */ public ContainerArcaneAssembler(int id, Inventory inv) { this(id, inv, (TileArcaneAssembler) null); @@ -90,6 +134,13 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu { public ContainerArcaneAssembler(int id, Inventory inv, TileArcaneAssembler tile) { super(ModMenuTypes.ARCANE_ASSEMBLER.get(), id); this.assembler = tile; + // 可视数据同步:服务端从 tile 读实时值;客户端存同步值(不依赖 BE 网络同步) + if (inv.player.level().isClientSide) { + this.visData = new ClientVisData(); + } else { + this.visData = new TileVisData(tile); + } + this.addDataSlots(this.visData); // ===== 1. 玩家背包 3行 ===== for (int r = 0; r < 3; r++) @@ -166,7 +217,16 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu { /** 客户端构造(IMenuTypeExtension 反射调用) */ public ContainerArcaneAssembler(int id, Inventory inv, RegistryFriendlyByteBuf buf) { - this(id, inv); + this(id, inv, resolveTile(inv, buf)); + } + + /** 临时诊断:确认客户端能否拿到 tile(定位后删除) */ + private static TileArcaneAssembler resolveTile(Inventory inv, RegistryFriendlyByteBuf buf) { + var pos = buf.readBlockPos(); + var be = inv.player.level().getBlockEntity(pos); + ThaumicEnergistics.LOG.info("[AA-DEBUG] client ctor: pos={}, be={}", + pos, be == null ? "null" : be.getClass().getSimpleName()); + return (TileArcaneAssembler) be; } private ItemStackHandler wrapMachineInventory() { diff --git a/src/main/java/thaumicenergistics/common/container/ContainerArcaneCraftingTerminal.java b/src/main/java/thaumicenergistics/common/container/ContainerArcaneCraftingTerminal.java index 7b289eb..fbffee4 100644 --- a/src/main/java/thaumicenergistics/common/container/ContainerArcaneCraftingTerminal.java +++ b/src/main/java/thaumicenergistics/common/container/ContainerArcaneCraftingTerminal.java @@ -88,6 +88,13 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC return 1; } + + @Override + public boolean mayPlace(ItemStack stack) { + //只允许 TCFunctionalItems.WandCastingItem 类型的物品放入 + return stack.getItem() instanceof TCFunctionalItems.WandCastingItem; + } + @Override public void setChanged() { super.setChanged(); diff --git a/src/main/java/thaumicenergistics/common/container/ContainerDistillationPatternEncoder.java b/src/main/java/thaumicenergistics/common/container/ContainerDistillationPatternEncoder.java index 12b0f18..d838089 100644 --- a/src/main/java/thaumicenergistics/common/container/ContainerDistillationPatternEncoder.java +++ b/src/main/java/thaumicenergistics/common/container/ContainerDistillationPatternEncoder.java @@ -5,6 +5,7 @@ import appeng.api.stacks.AEItemKey; import appeng.api.stacks.GenericStack; import appeng.core.definitions.AEItems; import net.minecraft.network.RegistryFriendlyByteBuf; +import net.neoforged.neoforge.network.PacketDistributor; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; @@ -21,6 +22,7 @@ import thaumcraft.common.research.ThaumometerScanManager; import thaumicenergistics.ThaumicEnergistics; import thaumicenergistics.common.integration.appeng.AEssentiaKey; import thaumicenergistics.common.integration.tc.TCReflection; +import thaumicenergistics.common.network.DistillationEncoderSetSourceC2SPacket; import thaumicenergistics.common.tiles.TileDistillationPatternEncoder; import thaumicenergistics.init.ModMenuTypes; import java.util.Arrays; @@ -104,14 +106,18 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { }); if (encoder != null) { - // 源物品槽:绑定 Tile 槽 0(持久化到方块 NBT,关闭 GUI 不丢失) + // 源物品槽:ghost 模板(背包拖入不消耗——实际物品还在手里;空手点击清空;JEI 拖拽设置模板) addSlot(new SlotItemHandler(encoder.inventory, TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, SLOT_SOURCE_X, SLOT_SOURCE_Y) { @Override public int getMaxStackSize() { return 1; } + @Override + public boolean mayPlace(ItemStack s) { return true; } + @Override + public boolean mayPickup(Player p) { return false; } }); addSlot(new SlotItemHandler(encoder.inventory, TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS, SLOT_BLANK_X, SLOT_BLANK_Y) { @Override - public boolean mayPlace(ItemStack s) { return true; } + public boolean mayPlace(ItemStack s) { return AEItems.BLANK_PATTERN.is(s); } @Override public int getMaxStackSize() { return 64; } }); @@ -125,10 +131,14 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { addSlot(new SlotItemHandler(dummy, TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, SLOT_SOURCE_X, SLOT_SOURCE_Y) { @Override public int getMaxStackSize() { return 1; } + @Override + public boolean mayPlace(ItemStack s) { return true; } + @Override + public boolean mayPickup(Player p) { return false; } }); addSlot(new SlotItemHandler(dummy, TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS, SLOT_BLANK_X, SLOT_BLANK_Y) { @Override - public boolean mayPlace(ItemStack s) { return true; } + public boolean mayPlace(ItemStack s) { return AEItems.BLANK_PATTERN.is(s); } @Override public int getMaxStackSize() { return 64; } }); @@ -150,6 +160,14 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { return ItemStack.EMPTY; } + /** 服务端:设置源物品(JEI 拖拽)——写入 encoder 槽 0 + 刷新 aspects。 */ + public void setSourceItemOnServer(ItemStack stack) { + if (encoder == null) return; + encoder.inventory.setStackInSlot(TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, stack.copy()); + encoder.setChanged(); + updateAspects(); + } + /** * 源物品的 aspects:源物品变化(含 NBT/扫描数据)时才调用 {@code getObjectAspects} 重新解析, * 否则返回缓存——每帧渲染调用仅做 O(1) 物品比较,不频繁请求 TC。 @@ -235,13 +253,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { ItemStack source = getSourceItem(); ItemStack blankSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS); ItemStack encodedSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_ENCODED_PATTERN); - ThaumicEnergistics.LOG.info("[DE] onEncodePattern: selected={}, selIdx={}, aspects={}, source={}, blank={}isBlank={}, encoded={}", - selected == null ? "null" : selected.getTag(), selectedAspectIndex, - getSourceAspects().length, - source.isEmpty() ? "EMPTY" : source.getItem().getDescriptionId(), - blankSlot.isEmpty() ? "EMPTY" : blankSlot.getItem().getDescriptionId(), - AEItems.BLANK_PATTERN.is(blankSlot), - encodedSlot.isEmpty() ? "EMPTY" : encodedSlot.getItem().getDescriptionId()); if (selected == null) return; @@ -251,8 +262,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { // 验证必须是 AE2 空白样板,否则不消耗不编码(日志定位 is() 运行时行为) if (!AEItems.BLANK_PATTERN.is(blankSlot)) { - ThaumicEnergistics.LOG.warn("[DE] Encode skipped: blank slot item={}, isBlankPattern={}", - blankSlot.getItem().getDescriptionId(), AEItems.BLANK_PATTERN.is(blankSlot)); return; } @@ -288,11 +297,18 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { Slot slot = slots.get(slotId); if (slotId == SOURCE_SLOT) { - // 标准拖放处理(SlotItemHandler 已限制槽内 1 个),刷新 aspects - super.clicked(slotId, dragType, clickType, player); + // ghost 模板语义:手持物品→设模板(不消耗,实际物品留在手里);空手→清空 + ItemStack carried = getCarried(); + ItemStack newSource = carried.isEmpty() ? ItemStack.EMPTY : carried.copyWithCount(1); + if (player.level().isClientSide) { + if (slotId < slots.size()) { + slots.get(slotId).set(newSource); + } + PacketDistributor.sendToServer(new DistillationEncoderSetSourceC2SPacket(containerId, newSource)); + } else { + setSourceItemOnServer(newSource); + } updateAspects(); - ThaumicEnergistics.LOG.info("[DE] source slot clicked: item={}, count={}, aspects={}", - getSourceItem().getItem().getDescriptionId(), getSourceItem().getCount(), getSourceAspects().length); return; } @@ -300,8 +316,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu { int aspectIdx = slotId - ASPECT_SLOT_START; // 始终记录用户选择(越界由 getSelectedAspect 校验),避免因 cachedAspects 状态导致服务端选择丢失 selectedAspectIndex = aspectIdx; - ThaumicEnergistics.LOG.info("[DE] aspect clicked: idx={}, cached={}, selected={}", - aspectIdx, cachedAspects.length, selectedAspectIndex); // selected 槽留空,图标由 GUI 绘制 aspectInventory.setStackInSlot(SELECTED_SLOT - ASPECT_SLOT_START, ItemStack.EMPTY); return; diff --git a/src/main/java/thaumicenergistics/common/container/ContainerKnowledgeInscriber.java b/src/main/java/thaumicenergistics/common/container/ContainerKnowledgeInscriber.java index 60b6074..b5ebc13 100644 --- a/src/main/java/thaumicenergistics/common/container/ContainerKnowledgeInscriber.java +++ b/src/main/java/thaumicenergistics/common/container/ContainerKnowledgeInscriber.java @@ -105,8 +105,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { super(ModMenuTypes.KNOWLEDGE_INSCRIBER.get(), id); this.tile = tile; this.playerInv = inv; - ThaumicEnergistics.LOG.info("[KLGE] ContainerKnowledgeInscriber opened, id={}, hasTile={}, isClient={}", - id, tile != null, inv.player.level().isClientSide()); for (int r = 0; r < 3; r++) for (int c = 0; c < 9; c++) @@ -240,14 +238,11 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { } activeRecipe = findArcaneRecipe(); - ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: activeRecipe={}", activeRecipe); if (activeRecipe != null) { ItemStack output = activeRecipe.getCraftingResult(craftingContainer); - ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: output={}", output.isEmpty() ? "EMPTY" : output.getItem().toString()); resultInventory.setStackInSlot(0, output); } else { - ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: no recipe matched → EMPTY"); resultInventory.setStackInSlot(0, ItemStack.EMPTY); } @@ -257,28 +252,21 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { private IArcaneRecipe findArcaneRecipe() { Level level = playerInv.player.level(); if (level == null) { - ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: level is null"); return null; } var recipes = ThaumcraftApi.getCraftingRecipes(); - ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: total recipes={}", recipes.size()); int arcaneCount = 0; for (Object recipe : recipes) { if (recipe instanceof IArcaneRecipe arcaneRecipe) { arcaneCount++; try { if (gridMatches(arcaneRecipe, craftingContainer)) { - ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: MATCH FOUND! recipe={}", - arcaneRecipe); return arcaneRecipe; } } catch (Exception e) { - ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: exception on recipe={}: {}", - arcaneRecipe, e.getMessage()); } } } - ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: no match among {} arcane recipes", arcaneCount); return null; } @@ -359,7 +347,8 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { private void sendSaveState() { if (tile != null && tile.getLevel() != null && !tile.getLevel().isClientSide) { - saveStateSlot.set(getSaveState().ordinal()); + CoreSaveState s = getSaveState(); + saveStateSlot.set(s.ordinal()); } } @@ -423,25 +412,32 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { public void clicked(int slotId, int dragType, ClickType clickType, Player player) { if (slotId >= 0 && slotId < slots.size()) { Slot slot = slots.get(slotId); + // 图案槽(核心已有配方,slotId 37..37+MAXIMUM_PATTERNS-1):点击加载该配方到合成网格 → 可保存/删除 + if (slotId >= 37 && slotId < 37 + MAXIMUM_PATTERNS) { + int patternIdx = slotId - 37; + ItemStack output = patternInventory.getStackInSlot(patternIdx); + if (!output.isEmpty()) { + ArcaneCraftingPattern pattern = kCoreHandler.getPatternForItem(output); + if (pattern != null) { + loadPatternToGrid(pattern, player); + } + } + return; + } if (slot instanceof GhostSlot ghostSlot) { int gridIndex = ghostSlot.index; ItemStack carried = getCarried(); ItemStack newStack = carried.isEmpty() ? ItemStack.EMPTY : carried.copyWithCount(1); - ThaumicEnergistics.LOG.info("[KLGE] clicked GhostSlot idx={} side={} stack={}", - gridIndex, player.level().isClientSide() ? "CLIENT" : "SERVER", - newStack.isEmpty() ? "EMPTY" : newStack.getItem().toString()); ghostSlot.set(newStack); if (player.level().isClientSide) { - ThaumicEnergistics.LOG.info("[KLGE] CLIENT branch → onCraftingChangedClient + C2S"); onCraftingChangedClient(); PacketDistributor.sendToServer( new KnowledgeInscriberGhostSlotPacket(containerId, gridIndex, newStack)); } else { - ThaumicEnergistics.LOG.info("[KLGE] SERVER branch → onCraftingChanged"); onCraftingChanged(); } return; @@ -453,8 +449,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { public void setGhostSlotOnServer(int slotIndex, ItemStack stack) { if (slotIndex < 0 || slotIndex >= CRAFTING_GRID_SIZE) return; ItemStack s = stack == null ? ItemStack.EMPTY : stack.copy(); - ThaumicEnergistics.LOG.info("[KLGE] setGhostSlotOnServer idx={} stack={}", - slotIndex, s.isEmpty() ? "EMPTY" : s.getItem().toString()); craftingContainer.setItem(slotIndex, s); int slotId = 37 + MAXIMUM_PATTERNS + slotIndex; if (slotId < slots.size() && slots.get(slotId) instanceof GhostSlot) { @@ -463,8 +457,37 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { onCraftingChanged(); } - private void onCraftingChangedClient() { - ThaumicEnergistics.LOG.info("[KLGE] onCraftingChangedClient"); + /** GhostSlot 的网格索引(0-8)。ghostSlot.index 是菜单槽序号(37+MAXIMUM_PATTERNS+idx),需换算。 */ + public int getGhostGridIndex(Slot slot) { + if (slot instanceof GhostSlot && slot.container == craftingContainer) { + return slot.index - (37 + MAXIMUM_PATTERNS); + } + return -1; + } + + /** 点击图案槽:把核心已有配方加载到合成网格(清空 + 填 9 格材料),供保存/删除。 */ + private void loadPatternToGrid(ArcaneCraftingPattern pattern, Player player) { + ItemStack[] ingredients = pattern.getIngredients(); + boolean client = player.level().isClientSide; + for (int i = 0; i < CRAFTING_GRID_SIZE; i++) { + ItemStack s = i < ingredients.length && ingredients[i] != null ? ingredients[i].copy() : ItemStack.EMPTY; + craftingContainer.setItem(i, s.copy()); + int slotId = 37 + MAXIMUM_PATTERNS + i; + if (slotId < slots.size() && slots.get(slotId) instanceof GhostSlot gs) { + gs.set(s.copy()); + } + if (client) { + PacketDistributor.sendToServer(new KnowledgeInscriberGhostSlotPacket(containerId, i, s.copy())); + } + } + if (client) { + onCraftingChangedClient(); + } else { + onCraftingChanged(); + } + } + + public void onCraftingChangedClient() { for (int i = 0; i < CRAFTING_GRID_SIZE; i++) { craftingInventory.setStackInSlot(i, craftingContainer.getItem(i).copy()); } @@ -472,7 +495,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu { } private void onCraftingChanged() { - ThaumicEnergistics.LOG.info("[KLGE] onCraftingChanged"); for (int i = 0; i < CRAFTING_GRID_SIZE; i++) { craftingInventory.setStackInSlot(i, craftingContainer.getItem(i).copy()); } diff --git a/src/main/java/thaumicenergistics/common/container/GuiArcaneAssembler.java b/src/main/java/thaumicenergistics/common/container/GuiArcaneAssembler.java index ab943c9..d9c0dbd 100644 --- a/src/main/java/thaumicenergistics/common/container/GuiArcaneAssembler.java +++ b/src/main/java/thaumicenergistics/common/container/GuiArcaneAssembler.java @@ -5,9 +5,8 @@ import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.player.Inventory; -import thaumcraft.api.aspects.Aspect; +import net.minecraft.world.inventory.ContainerData; import thaumicenergistics.ThaumicEnergistics; -import thaumicenergistics.common.tiles.TileArcaneAssembler; /** * 奥术装配器 GUI。 @@ -27,17 +26,15 @@ public class GuiArcaneAssembler extends AbstractContainerScreen 0) { anyVis = true; break; } + } + ratio = anyVis ? 1f : 0f; } } - // 空bar暗色背景(满bar不在此处,由纹理裁剪提供) - g.fill(bx, by, bx + VIS_BAR_W, by + VIS_BAR_H, 0x77222222); - - // 从纹理Y=202处裁剪满bar,按高度百分比叠加 + // 从纹理 Y=202 裁剪满亮条,按高度百分比叠加 int fillHeight = Math.round(VIS_BAR_H * ratio); if (fillHeight > 0) { int srcX = VIS_BAR_X + i * VIS_BAR_STEP; - int srcY = VIS_BAR_Y + (VIS_BAR_H - fillHeight); - g.blit(TEX, bx, by + (VIS_BAR_H - fillHeight), srcX, srcY, VIS_BAR_W, fillHeight); + int srcY = VIS_FULL_Y + (VIS_BAR_H - fillHeight); + int dstY = by + (VIS_BAR_H - fillHeight); + g.blit(TEX, bx, dstY, srcX, srcY, VIS_BAR_W, fillHeight); } - // bar边框 + // bar 边框 g.fill(bx - 1, by - 1, bx, by + VIS_BAR_H + 1, 0xAA000000); g.fill(bx + VIS_BAR_W, by - 1, bx + VIS_BAR_W + 1, by + VIS_BAR_H + 1, 0xAA000000); g.fill(bx - 1, by - 1, bx + VIS_BAR_W + 1, by, 0xAA000000); diff --git a/src/main/java/thaumicenergistics/common/container/GuiArcaneCraftingTerminal.java b/src/main/java/thaumicenergistics/common/container/GuiArcaneCraftingTerminal.java index b0c6310..21219f3 100644 --- a/src/main/java/thaumicenergistics/common/container/GuiArcaneCraftingTerminal.java +++ b/src/main/java/thaumicenergistics/common/container/GuiArcaneCraftingTerminal.java @@ -47,7 +47,7 @@ public class GuiArcaneCraftingTerminal extends MEStorageScreen { public static PartItemEssentiaExportBus create() { - return new PartItemEssentiaExportBus(new Item.Properties().stacksTo(64)); + return new PartItemEssentiaExportBus(new Properties().stacksTo(64)); } public PartItemEssentiaExportBus(Properties properties) { diff --git a/src/main/java/thaumicenergistics/common/items/PartItemEssentiaImportBus.java b/src/main/java/thaumicenergistics/common/items/PartItemEssentiaImportBus.java index 93156f7..24e4e54 100644 --- a/src/main/java/thaumicenergistics/common/items/PartItemEssentiaImportBus.java +++ b/src/main/java/thaumicenergistics/common/items/PartItemEssentiaImportBus.java @@ -16,7 +16,7 @@ import java.util.List; public class PartItemEssentiaImportBus extends Item implements IPartItem { public static PartItemEssentiaImportBus create() { - return new PartItemEssentiaImportBus(new Item.Properties().stacksTo(64)); + return new PartItemEssentiaImportBus(new Properties().stacksTo(64)); } public PartItemEssentiaImportBus(Properties properties) { diff --git a/src/main/java/thaumicenergistics/common/items/PartItemEssentiaStorageBus.java b/src/main/java/thaumicenergistics/common/items/PartItemEssentiaStorageBus.java index 05817b1..71484c0 100644 --- a/src/main/java/thaumicenergistics/common/items/PartItemEssentiaStorageBus.java +++ b/src/main/java/thaumicenergistics/common/items/PartItemEssentiaStorageBus.java @@ -16,7 +16,7 @@ import java.util.List; public class PartItemEssentiaStorageBus extends Item implements IPartItem { public static PartItemEssentiaStorageBus create() { - return new PartItemEssentiaStorageBus(new Item.Properties().stacksTo(64)); + return new PartItemEssentiaStorageBus(new Properties().stacksTo(64)); } public PartItemEssentiaStorageBus(Properties properties) { diff --git a/src/main/java/thaumicenergistics/common/network/DistillationEncoderSetSourceC2SPacket.java b/src/main/java/thaumicenergistics/common/network/DistillationEncoderSetSourceC2SPacket.java new file mode 100644 index 0000000..47b51c1 --- /dev/null +++ b/src/main/java/thaumicenergistics/common/network/DistillationEncoderSetSourceC2SPacket.java @@ -0,0 +1,45 @@ +package thaumicenergistics.common.network; + +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.world.item.ItemStack; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import thaumicenergistics.ThaumicEnergistics; +import thaumicenergistics.common.container.ContainerDistillationPatternEncoder; + +/** + * 客户端→服务端 网络包:蒸馏编码台设置源物品(JEI 拖拽)。 + * 源物品槽是真实 SlotItemHandler(非 ghost),拖拽需服务端权威写入 encoder 槽 0 + 刷新 aspects。 + */ +public record DistillationEncoderSetSourceC2SPacket( + int containerId, + ItemStack stack +) implements CustomPacketPayload { + + public static final Type TYPE = + new Type<>(ThaumicEnergistics.id("de_set_source")); + + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.VAR_INT, DistillationEncoderSetSourceC2SPacket::containerId, + ItemStack.OPTIONAL_STREAM_CODEC, DistillationEncoderSetSourceC2SPacket::stack, + DistillationEncoderSetSourceC2SPacket::new + ); + + @Override + public Type type() { + return TYPE; + } + + /** 服务端处理:找到对应菜单,写入源物品槽并刷新 aspects。 */ + public void handleOnServer(IPayloadContext ctx) { + ctx.enqueueWork(() -> { + if (ctx.player().containerMenu.containerId == this.containerId + && ctx.player().containerMenu instanceof ContainerDistillationPatternEncoder menu) { + menu.setSourceItemOnServer(this.stack); + } + }); + } +} diff --git a/src/main/java/thaumicenergistics/common/network/ThENetworking.java b/src/main/java/thaumicenergistics/common/network/ThENetworking.java index 4d9fc36..7f5ba75 100644 --- a/src/main/java/thaumicenergistics/common/network/ThENetworking.java +++ b/src/main/java/thaumicenergistics/common/network/ThENetworking.java @@ -29,6 +29,13 @@ public final class ThENetworking { (pkt, ctx) -> pkt.handleOnServer(ctx) ); + // DistillationEncoderSetSourceC2SPacket — C2S(蒸馏编码台设置源物品——JEI 拖拽) + registrar.playToServer( + DistillationEncoderSetSourceC2SPacket.TYPE, + DistillationEncoderSetSourceC2SPacket.STREAM_CODEC, + (pkt, ctx) -> pkt.handleOnServer(ctx) + ); + // CellWorkbenchC2SPacket — C2S(客户端→服务端,源质元件工作台分区操作) registrar.playToServer( CellWorkbenchC2SPacket.TYPE, diff --git a/src/main/java/thaumicenergistics/common/storage/TransportPermissions.java b/src/main/java/thaumicenergistics/common/storage/TransportPermissions.java index e5c58f7..4da93f7 100644 --- a/src/main/java/thaumicenergistics/common/storage/TransportPermissions.java +++ b/src/main/java/thaumicenergistics/common/storage/TransportPermissions.java @@ -19,8 +19,8 @@ public class TransportPermissions implements IThETransportPermissions { @Override public boolean addTileToInject(Class c, int cap) { return tileInject.putIfAbsent(c, cap) == null; } @Override - public java.util.OptionalLong getCapacityForTile(Class c) { - Integer v = tileExtract.get(c); return v != null ? java.util.OptionalLong.of(v) : java.util.OptionalLong.empty(); + public OptionalLong getCapacityForTile(Class c) { + Integer v = tileExtract.get(c); return v != null ? OptionalLong.of(v) : OptionalLong.empty(); } @Override public boolean canExtract(BlockEntity tile) { return tile != null && tileExtract.containsKey(tile.getClass()); } diff --git a/src/main/java/thaumicenergistics/common/tiles/TileArcaneAssembler.java b/src/main/java/thaumicenergistics/common/tiles/TileArcaneAssembler.java index 6e3abc9..0ef7a2c 100644 --- a/src/main/java/thaumicenergistics/common/tiles/TileArcaneAssembler.java +++ b/src/main/java/thaumicenergistics/common/tiles/TileArcaneAssembler.java @@ -75,7 +75,7 @@ public class TileArcaneAssembler extends ThETileBase private static final double ACTIVE_POWER = 1.5; private static final double WARP_POWER_PERCENT = 0.15; private static final int MAX_SPEED_UPGRADES = 4; - private static final int VIS_REPLENISH_INTERVAL = 5; + private static final int VIS_REPLENISH_INTERVAL = 20; /** 每次实际 vis 变化累计达到该值才调用一次 setChanged(约 4×5=20 tick),避免合成期间 chunk 持续处于 unsaved 状态。 */ private static final int SET_CHANGED_INTERVAL = 4; @@ -141,7 +141,7 @@ public class TileArcaneAssembler extends ThETileBase super(ModBlockEntities.ARCANE_ASSEMBLER.get(), pos, state); mainNode = GridHelper.createManagedNode(this, nodeListener) - .setVisualRepresentation(thaumicenergistics.init.ModItems.ARCANE_ASSEMBLER_ITEM.get()) + .setVisualRepresentation(ModItems.ARCANE_ASSEMBLER_ITEM.get()) .setInWorldNode(true) .setTagName("proxy") .setFlags(GridFlags.REQUIRE_CHANNEL) @@ -296,13 +296,13 @@ public class TileArcaneAssembler extends ThETileBase } /** - * replenishVis 抽到 vis 时调用:累计变化次数,每 SET_CHANGED_INTERVAL 次才标记一次 chunk dirty。 - * 降低合成期间持续磁盘写放大;storedVis 最终仍会在合成完成路径(craftingTick)或区块保存时落盘。 + * replenishVis 抽到 vis 时调用:累计变化次数,每 SET_CHANGED_INTERVAL 次才 markForUpdate 一次。 + * markForUpdate = setChanged + sendBlockUpdated(同步 storedVis 到客户端 BE,GUI 进度条依赖客户端 storedVis)。 */ private void maybeMarkVisDirty() { if (++visSetChangedCounter >= SET_CHANGED_INTERVAL) { visSetChangedCounter = 0; - setChanged(); + markForUpdate(); } } @@ -360,6 +360,8 @@ public class TileArcaneAssembler extends ThETileBase if (extracted >= need * 0.9) { craftTickCounter += ticksSinceLast; ThaumicEnergistics.LOG.debug("[AA] craftingTick: 推进 {}/{} (抽电 {}/{})", craftTickCounter, ticksPerCraft, extracted, need); + // 节流同步合成进度到客户端(GUI 第7格进度条动画) + maybeMarkVisDirty(); } else { ThaumicEnergistics.LOG.debug("[AA] craftingTick: 能量不足 (抽到 {}/{}),不推进", extracted, need); } @@ -375,7 +377,7 @@ public class TileArcaneAssembler extends ThETileBase int current = storedVis.getAmount(aspect); storedVis.reduce(aspect, Math.min(requiredCvis, current)); } - setChanged(); + maybeMarkVisDirty(); } private boolean hasEnoughVisForCraft() { @@ -512,7 +514,7 @@ public class TileArcaneAssembler extends ThETileBase @Override public PatternContainerGroup getCraftingMachineInfo() { - ItemStack icon = new ItemStack(thaumicenergistics.init.ModItems.ARCANE_ASSEMBLER_ITEM.get()); + ItemStack icon = new ItemStack(ModItems.ARCANE_ASSEMBLER_ITEM.get()); return new PatternContainerGroup( AEItemKey.of(icon), net.minecraft.network.chat.Component.translatable("block.thaumicenergistics.arcane_assembler"), @@ -575,7 +577,7 @@ public class TileArcaneAssembler extends ThETileBase */ public void onMemoryCardActivate(net.minecraft.world.entity.player.Player player, appeng.api.implementations.items.IMemoryCard memoryCard, - net.minecraft.world.item.ItemStack heldItem) { + ItemStack heldItem) { Short freq = heldItem.get(appeng.api.ids.AEComponents.EXPORTED_P2P_FREQUENCY); if (freq != null) { // 卡上带 P2P 频率(从 vis_interface shift+右键复制来的)→ 设为 vis 源 @@ -643,6 +645,9 @@ public class TileArcaneAssembler extends ThETileBase if (level != null) { setChanged(); level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); + // 关键:blockEntityChanged 触发 BE 数据(getUpdateTag)同步到客户端。 + // setChanged 只标记区块保存、sendBlockUpdated 只触发重渲染——缺这个 GUI 永远拿不到 storedVis/isCrafting。 + level.blockEntityChanged(worldPosition); } } diff --git a/src/main/java/thaumicenergistics/common/tiles/TileEssentiaProvider.java b/src/main/java/thaumicenergistics/common/tiles/TileEssentiaProvider.java index 70a5a63..e73b5c7 100644 --- a/src/main/java/thaumicenergistics/common/tiles/TileEssentiaProvider.java +++ b/src/main/java/thaumicenergistics/common/tiles/TileEssentiaProvider.java @@ -37,14 +37,14 @@ public class TileEssentiaProvider extends TileProviderBase implements IEssentiaT } /** getEssentiaList 短时缓存:TC 管道 suction 每 tick 调 getEssentiaType,避免每 tick 全量遍历。 */ - private List cachedEssentiaList = java.util.List.of(); + private List cachedEssentiaList = List.of(); private int essentiaListCacheTimer = 0; private static final int ESSENTIA_LIST_CACHE_TICKS = 20; - private List getCachedEssentiaList() { + private List getCachedEssentiaList() { if (essentiaListCacheTimer <= 0) { essentiaListCacheTimer = ESSENTIA_LIST_CACHE_TICKS; - cachedEssentiaList = essentiaGrid != null ? essentiaGrid.getEssentiaList() : java.util.List.of(); + cachedEssentiaList = essentiaGrid != null ? essentiaGrid.getEssentiaList() : List.of(); } return cachedEssentiaList; } diff --git a/src/main/java/thaumicenergistics/init/ModBlocks.java b/src/main/java/thaumicenergistics/init/ModBlocks.java index 4069d24..505ec2c 100644 --- a/src/main/java/thaumicenergistics/init/ModBlocks.java +++ b/src/main/java/thaumicenergistics/init/ModBlocks.java @@ -39,12 +39,13 @@ public final class ModBlocks { return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide()); } if (!level.isClientSide) { - player.openMenu(state.getMenuProvider(level, pos)); + ((net.neoforged.neoforge.common.extensions.IPlayerExtension) player) + .openMenu(state.getMenuProvider(level, pos), pos); } return net.minecraft.world.ItemInteractionResult.SUCCESS; } @Override - protected net.minecraft.world.MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { + protected MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { TileArcaneAssembler tile = (TileArcaneAssembler) level.getBlockEntity(pos); return new SimpleMenuProvider( (id, inv, p) -> new thaumicenergistics.common.container.ContainerArcaneAssembler(id, inv, tile), @@ -68,7 +69,7 @@ public final class ModBlocks { return net.minecraft.world.ItemInteractionResult.SUCCESS; } @Override - protected net.minecraft.world.MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { + protected MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { TileKnowledgeInscriber tile = (TileKnowledgeInscriber) level.getBlockEntity(pos); return new SimpleMenuProvider( (id, inv, p) -> new thaumicenergistics.common.container.ContainerKnowledgeInscriber(id, inv, tile), @@ -91,7 +92,7 @@ public final class ModBlocks { return net.minecraft.world.ItemInteractionResult.SUCCESS; } @Override - protected net.minecraft.world.MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { + protected MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { TileDistillationPatternEncoder tile = (TileDistillationPatternEncoder) level.getBlockEntity(pos); return new SimpleMenuProvider( (id, inv, p) -> new thaumicenergistics.common.container.ContainerDistillationPatternEncoder(id, inv, tile), @@ -109,7 +110,7 @@ public final class ModBlocks { return net.minecraft.world.ItemInteractionResult.SUCCESS; } @Override - protected net.minecraft.world.MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { + protected MenuProvider getMenuProvider(BlockState state, net.minecraft.world.level.Level level, BlockPos pos) { net.minecraft.world.level.block.entity.BlockEntity be = level.getBlockEntity(pos); if (be instanceof TileEssentiaCellWorkbench workbench) { return new SimpleMenuProvider((id, inv, p) -> new thaumicenergistics.common.container.ContainerEssentiaCellWorkbench(id, inv, workbench), diff --git a/src/main/java/thaumicenergistics/integration/jei/ArcaneCraftingTransferInfo.java b/src/main/java/thaumicenergistics/integration/jei/ArcaneCraftingTransferInfo.java index adbde55..c618da0 100644 --- a/src/main/java/thaumicenergistics/integration/jei/ArcaneCraftingTransferInfo.java +++ b/src/main/java/thaumicenergistics/integration/jei/ArcaneCraftingTransferInfo.java @@ -45,11 +45,11 @@ public class ArcaneCraftingTransferInfo @Override public List getRecipeSlots(ContainerArcaneCraftingTerminal menu, RecipeHolder recipe) { - return menu.getSlots(appeng.menu.SlotSemantics.CRAFTING_GRID); + return menu.getSlots(SlotSemantics.CRAFTING_GRID); } @Override public List getInventorySlots(ContainerArcaneCraftingTerminal menu, RecipeHolder recipe) { - return menu.getSlots(appeng.menu.SlotSemantics.PLAYER_INVENTORY); + return menu.getSlots(SlotSemantics.PLAYER_INVENTORY); } } diff --git a/src/main/java/thaumicenergistics/integration/jei/DistillationEncoderGhostIngredientHandler.java b/src/main/java/thaumicenergistics/integration/jei/DistillationEncoderGhostIngredientHandler.java new file mode 100644 index 0000000..e4d7a36 --- /dev/null +++ b/src/main/java/thaumicenergistics/integration/jei/DistillationEncoderGhostIngredientHandler.java @@ -0,0 +1,55 @@ +package thaumicenergistics.integration.jei; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.world.item.ItemStack; + +import mezz.jei.api.gui.handlers.IGhostIngredientHandler; +import mezz.jei.api.ingredients.ITypedIngredient; + +import net.neoforged.neoforge.network.PacketDistributor; + +import thaumicenergistics.common.container.GuiDistillationPatternEncoder; +import thaumicenergistics.common.network.DistillationEncoderSetSourceC2SPacket; + +/** + * 蒸馏编码台 JEI 拖拽:从 JEI 拉取物品到源物品槽(15,69)→ C2S 设置源物品。 + */ +public class DistillationEncoderGhostIngredientHandler + implements IGhostIngredientHandler { + + /** 与 ContainerDistillationPatternEncoder.SLOT_SOURCE_X/Y 一致(15, 69)。 */ + private static final int SOURCE_SLOT_X = 15; + private static final int SOURCE_SLOT_Y = 69; + + @Override + public List> getTargetsTyped(GuiDistillationPatternEncoder gui, ITypedIngredient ingredient, boolean doStart) { + List> targets = new ArrayList<>(); + if (!(ingredient.getIngredient() instanceof ItemStack)) { + return targets; + } + int wx = gui.getGuiLeft() + SOURCE_SLOT_X; + int wy = gui.getGuiTop() + SOURCE_SLOT_Y; + targets.add(new Target<>() { + @Override + public Rect2i getArea() { + return new Rect2i(wx, wy, 16, 16); + } + + @Override + public void accept(I ingredient) { + if (ingredient instanceof ItemStack stack) { + PacketDistributor.sendToServer(new DistillationEncoderSetSourceC2SPacket( + gui.getMenu().containerId, stack.copyWithCount(1))); + } + } + }); + return targets; + } + + @Override + public void onComplete() { + } +} diff --git a/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberArcaneRecipeTransfer.java b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberArcaneRecipeTransfer.java new file mode 100644 index 0000000..4813cdd --- /dev/null +++ b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberArcaneRecipeTransfer.java @@ -0,0 +1,93 @@ +package thaumicenergistics.integration.jei; + +import java.util.List; +import java.util.Optional; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; + +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.transfer.IRecipeTransferError; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandler; +import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper; + +import net.neoforged.neoforge.network.PacketDistributor; + +import thaumcraft.integration.jei.JeiArcaneRecipe; +import thaumcraft.integration.jei.ThaumcraftJeiPlugin; +import thaumicenergistics.common.container.ContainerKnowledgeInscriber; +import thaumicenergistics.common.container.slot.GhostSlot; +import thaumicenergistics.common.network.KnowledgeInscriberGhostSlotPacket; +import thaumicenergistics.init.ModMenuTypes; + +/** + * 知识记录仪 JEI 奥术配方 "+" 一键放置: + * 点 "+" → 把配方材料填入 3×3 ghost 合成网格(有序按位置 / 无序填前 N 格)。 + * 复用 KnowledgeInscriberGhostSlotPacket 同步服务端(客户端同时刷新产物)。 + */ +public class KnowledgeInscriberArcaneRecipeTransfer + implements IRecipeTransferHandler { + + @SuppressWarnings("unused") + private final IRecipeTransferHandlerHelper helper; + + public KnowledgeInscriberArcaneRecipeTransfer(IRecipeTransferHandlerHelper helper) { + this.helper = helper; + } + + @Override + public Class getContainerClass() { + return ContainerKnowledgeInscriber.class; + } + + @Override + public Optional> getMenuType() { + return Optional.of(ModMenuTypes.KNOWLEDGE_INSCRIBER.get()); + } + + @Override + public RecipeType getRecipeType() { + return ThaumcraftJeiPlugin.ARCANE; + } + + @Override + public IRecipeTransferError transferRecipe( + ContainerKnowledgeInscriber menu, + JeiArcaneRecipe recipe, + mezz.jei.api.gui.ingredient.IRecipeSlotsView display, + net.minecraft.world.entity.player.Player player, + boolean maxTransfer, + boolean doTransfer) { + // 预览:始终可转移(ghost 槽不消耗物品) + if (!doTransfer) { + return null; + } + + // 构建 9 格模板(有序配方 inputs 为 3×3 展开含空位;无序配方为压缩列表——填前 N 格) + NonNullList template = NonNullList.withSize(9, ItemStack.EMPTY); + List> inputs = recipe.inputs(); + for (int i = 0; i < template.size() && i < inputs.size(); i++) { + List variants = inputs.get(i); + if (variants != null && !variants.isEmpty()) { + template.set(i, variants.get(0).copyWithCount(1)); + } + } + + // 按 ghost 槽顺序填入 + C2S 同步服务端 + 客户端刷新产物 + List ghostSlots = menu.slots.stream() + .filter(s -> s instanceof GhostSlot) + .map(s -> (GhostSlot) s) + .toList(); + for (int i = 0; i < template.size(); i++) { + ItemStack stack = template.get(i); + if (stack.isEmpty() || i >= ghostSlots.size()) continue; + ghostSlots.get(i).set(stack); + PacketDistributor.sendToServer( + new KnowledgeInscriberGhostSlotPacket(menu.containerId, i, stack)); + } + menu.onCraftingChangedClient(); + + return null; + } +} diff --git a/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberGhostIngredientHandler.java b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberGhostIngredientHandler.java new file mode 100644 index 0000000..27c6935 --- /dev/null +++ b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberGhostIngredientHandler.java @@ -0,0 +1,65 @@ +package thaumicenergistics.integration.jei; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.world.item.ItemStack; + +import mezz.jei.api.gui.handlers.IGhostIngredientHandler; +import mezz.jei.api.ingredients.ITypedIngredient; + +import net.neoforged.neoforge.network.PacketDistributor; + +import thaumicenergistics.common.container.GuiKnowledgeInscriber; +import thaumicenergistics.common.container.slot.GhostSlot; +import thaumicenergistics.common.network.KnowledgeInscriberGhostSlotPacket; + +/** + * 知识记录仪 JEI 拖拽:从 JEI 拉取物品到 3×3 合成网格(ghost 槽)。 + * 复用已有的 GhostSlot + KnowledgeInscriberGhostSlotPacket(客户端 set + C2S 同步服务端)。 + */ +public class KnowledgeInscriberGhostIngredientHandler + implements IGhostIngredientHandler { + + @Override + public List> getTargetsTyped(GuiKnowledgeInscriber gui, ITypedIngredient ingredient, boolean doStart) { + List> targets = new ArrayList<>(); + if (!(ingredient.getIngredient() instanceof ItemStack)) { + return targets; + } + for (var slot : gui.getMenu().slots) { + if (slot instanceof GhostSlot ghostSlot) { + int fx = slot.x; + int fy = slot.y; + targets.add(new Target<>() { + @Override + public Rect2i getArea() { + return new Rect2i(gui.getGuiLeft() + fx, gui.getGuiTop() + fy, 16, 16); + } + + @Override + public void accept(I ingredient) { + if (ingredient instanceof ItemStack stack) { + ItemStack s = stack.copyWithCount(1); + ghostSlot.set(s); + // 与手动点击一致:客户端刷新产物计算 + C2S 同步服务端 + if (gui.getMenu() instanceof thaumicenergistics.common.container.ContainerKnowledgeInscriber menu) { + menu.onCraftingChangedClient(); + } + int gridIndex = gui.getMenu() instanceof thaumicenergistics.common.container.ContainerKnowledgeInscriber m + ? m.getGhostGridIndex(ghostSlot) : -1; + PacketDistributor.sendToServer( + new KnowledgeInscriberGhostSlotPacket(gui.getMenu().containerId, gridIndex, s)); + } + } + }); + } + } + return targets; + } + + @Override + public void onComplete() { + } +} diff --git a/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberTransferInfo.java b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberTransferInfo.java new file mode 100644 index 0000000..af74099 --- /dev/null +++ b/src/main/java/thaumicenergistics/integration/jei/KnowledgeInscriberTransferInfo.java @@ -0,0 +1,62 @@ +package thaumicenergistics.integration.jei; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; + +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.transfer.IRecipeTransferInfo; + +import thaumcraft.integration.jei.JeiArcaneRecipe; +import thaumcraft.integration.jei.ThaumcraftJeiPlugin; +import thaumicenergistics.common.container.ContainerKnowledgeInscriber; +import thaumicenergistics.common.container.slot.GhostSlot; +import thaumicenergistics.init.ModMenuTypes; + +/** + * 知识记录仪 JEI 奥术配方转移槽位映射(让 JEI 显示 "+" 按钮)。 + * recipeSlots = 3×3 ghost 合成网格;inventorySlots = 玩家背包。 + */ +public class KnowledgeInscriberTransferInfo + implements IRecipeTransferInfo { + + @Override + public Class getContainerClass() { + return ContainerKnowledgeInscriber.class; + } + + @Override + public Optional> getMenuType() { + return Optional.of(ModMenuTypes.KNOWLEDGE_INSCRIBER.get()); + } + + @Override + public RecipeType getRecipeType() { + return ThaumcraftJeiPlugin.ARCANE; + } + + @Override + public boolean canHandle(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) { + return true; + } + + @Override + public List getRecipeSlots(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) { + List slots = new ArrayList<>(); + for (Slot slot : menu.slots) { + if (slot instanceof GhostSlot) { + slots.add(slot); + } + } + return slots; + } + + @Override + public List getInventorySlots(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) { + // 玩家背包槽先于机器槽加入(0-35) + return menu.slots.subList(0, 36); + } +} diff --git a/src/main/java/thaumicenergistics/integration/jei/ThEJeiPlugin.java b/src/main/java/thaumicenergistics/integration/jei/ThEJeiPlugin.java index 311e106..ccb549e 100644 --- a/src/main/java/thaumicenergistics/integration/jei/ThEJeiPlugin.java +++ b/src/main/java/thaumicenergistics/integration/jei/ThEJeiPlugin.java @@ -90,11 +90,24 @@ public class ThEJeiPlugin implements IModPlugin { // 注册 IRecipeTransferHandler 让点击 "+" 时走 ME 网络 registration.addRecipeTransferHandler(new ThEUseCraftingRecipeTransfer(registration.getTransferHelper()), CRAFTING); registration.addRecipeTransferHandler(new ThEUseArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE); + + // 知识记录仪:奥术配方 "+" 一键放置到 3×3 ghost 合成网格 + registration.addRecipeTransferHandler(new KnowledgeInscriberTransferInfo()); + registration.addRecipeTransferHandler( + new KnowledgeInscriberArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE); } @Override public void registerGuiHandlers(IGuiHandlerRegistration registration) { // 源质元件工作台:从 JEI 拖拽要素到分区格添加分区(纯 JEI API,可选功能) registration.addGhostIngredientHandler(GuiEssentiaCellWorkbench.class, new CellWorkbenchGhostIngredientHandler()); + // 知识记录仪:从 JEI 拉取物品到 3×3 合成网格(复用 GhostSlot + KnowledgeInscriberGhostSlotPacket) + registration.addGhostIngredientHandler( + thaumicenergistics.common.container.GuiKnowledgeInscriber.class, + new KnowledgeInscriberGhostIngredientHandler()); + // 蒸馏编码台:从 JEI 拉取物品到源物品槽(C2S 设置源物品) + registration.addGhostIngredientHandler( + thaumicenergistics.common.container.GuiDistillationPatternEncoder.class, + new DistillationEncoderGhostIngredientHandler()); } } diff --git a/src/main/resources/assets/ae2/screens/arcane_crafting_terminal.json b/src/main/resources/assets/ae2/screens/arcane_crafting_terminal.json index a86657c..1978d9c 100644 --- a/src/main/resources/assets/ae2/screens/arcane_crafting_terminal.json +++ b/src/main/resources/assets/ae2/screens/arcane_crafting_terminal.json @@ -12,7 +12,7 @@ "bottom": 140 }, "STORAGE": { - "left": 136, + "left": 134, "bottom": 163 } }, diff --git a/src/main/resources/assets/thaumicenergistics/models/block/infusion_provider.json b/src/main/resources/assets/thaumicenergistics/models/block/infusion_provider.json index 6ac41bb..b1f27bd 100644 --- a/src/main/resources/assets/thaumicenergistics/models/block/infusion_provider.json +++ b/src/main/resources/assets/thaumicenergistics/models/block/infusion_provider.json @@ -1 +1,6 @@ -{"parent":"minecraft:block/cube_all","textures":{"all":"thaumicenergistics:block/infusion_provider"}} +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "thaumicenergistics:block/infusion_provider" + } +}