提交
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
+32
-18
@@ -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;
|
||||
|
||||
+45
-23
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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<ContainerArcaneA
|
||||
ThaumicEnergistics.MODID, "textures/gui/arcane_assembler.png");
|
||||
|
||||
// ===== 第4块 Vis条 =====
|
||||
private static final int VIS_BAR_X = 42;
|
||||
private static final int VIS_BAR_Y = 202;
|
||||
/** 纹理中空槽(暗色背景)的 Y 坐标 = 进度条在 GUI 中的显示位置 */
|
||||
private static final int VIS_EMPTY_Y = 87;
|
||||
/** 纹理中满亮条的 Y 坐标(裁剪源,不直接显示) */
|
||||
private static final int VIS_FULL_Y = 202;
|
||||
private static final int VIS_BAR_W = 4;
|
||||
private static final int VIS_BAR_H = 16;
|
||||
private static final int VIS_BAR_STEP = 18;
|
||||
|
||||
// ===== 6原质 =====
|
||||
private static final Aspect[] VIS_ASPECTS = {
|
||||
Aspect.AIR, Aspect.FIRE, Aspect.WATER,
|
||||
Aspect.EARTH, Aspect.ORDER, Aspect.ENTROPY
|
||||
};
|
||||
/** 贴图规范起始 X = 41(步长18:41,59,77,95,113,131,149) */
|
||||
private static final int VIS_BAR_X = 41;
|
||||
|
||||
public GuiArcaneAssembler(ContainerArcaneAssembler menu, Inventory inv, Component title) {
|
||||
super(menu, inv, title);
|
||||
@@ -64,35 +61,48 @@ public class GuiArcaneAssembler extends AbstractContainerScreen<ContainerArcaneA
|
||||
// 第3块:右下正方形 68×68
|
||||
g.blit(TEX, left + 180, top + 129, 180, 129, 68, 68);
|
||||
|
||||
// ===== 第4块:Vis条(纹理裁剪方式,仿1.7.10 drawVisBar) =====
|
||||
TileArcaneAssembler tile = this.menu.assembler;
|
||||
// ===== 第4块:Vis条(1.7.10 裁剪方式:Y=87 空槽背景 + Y=202 亮条裁剪) =====
|
||||
// 数据源:容器同步(menu.visData,服务端每 tick 同步 storedVis/isCrafting/进度——不依赖 BE 网络同步)
|
||||
ContainerData data = this.menu.visData;
|
||||
final int maxCvis = 187 * 10;
|
||||
for (int i = 0; i < 7; i++) {
|
||||
int bx = left + VIS_BAR_X + i * VIS_BAR_STEP;
|
||||
int by = top + VIS_BAR_Y;
|
||||
int by = top + VIS_EMPTY_Y;
|
||||
|
||||
// 从纹理 Y=87 裁剪空槽背景
|
||||
g.blit(TEX, bx, by, VIS_BAR_X + i * VIS_BAR_STEP, VIS_EMPTY_Y, VIS_BAR_W, VIS_BAR_H);
|
||||
|
||||
float ratio = 0f;
|
||||
if (tile != null) {
|
||||
if (i < 6) {
|
||||
int cvis = tile.getStoredVis() != null ? tile.getStoredVis().getAmount(VIS_ASPECTS[i]) : 0;
|
||||
ratio = Math.min(1f, (float) cvis / maxCvis);
|
||||
if (i < 6) {
|
||||
// 1-6格:源质充能比例
|
||||
int cvis = data.get(ContainerArcaneAssembler.DATA_AIR + i);
|
||||
ratio = Math.min(1f, (float) cvis / maxCvis);
|
||||
} else {
|
||||
// 第7格:非合成时要素接入常亮,合成时显示渐变进度
|
||||
if (data.get(ContainerArcaneAssembler.DATA_CRAFTING) == 1) {
|
||||
int tick = data.get(ContainerArcaneAssembler.DATA_CRAFT_TICK);
|
||||
int total = Math.max(1, data.get(ContainerArcaneAssembler.DATA_TICKS_PER_CRAFT));
|
||||
ratio = Math.min(1f, (float) tick / total);
|
||||
} else {
|
||||
ratio = tile.getCraftProgress();
|
||||
// 要素接入达成:任一原质充入(源质已从网络流入)→ 常亮
|
||||
boolean anyVis = false;
|
||||
for (int a = 0; a < 6; a++) {
|
||||
if (data.get(ContainerArcaneAssembler.DATA_AIR + a) > 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);
|
||||
|
||||
@@ -47,7 +47,7 @@ public class GuiArcaneCraftingTerminal extends MEStorageScreen<ContainerArcaneCr
|
||||
if (cachedAnchorY < 0) {
|
||||
for (var slot : this.menu.slots) {
|
||||
// CRAFTING_RESULT 槽在 style JSON 中 bottom:140
|
||||
if (slot instanceof appeng.menu.slot.AppEngCraftingSlot) {
|
||||
if (slot instanceof AppEngCraftingSlot) {
|
||||
cachedAnchorY = slot.y;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -383,11 +383,11 @@ public final class RecipeRegistration {
|
||||
return new ItemStack(s.get());
|
||||
}
|
||||
private static ItemStack tc(String name) {
|
||||
return new ItemStack(net.minecraft.core.registries.BuiltInRegistries.ITEM.get(
|
||||
return new ItemStack(BuiltInRegistries.ITEM.get(
|
||||
ResourceLocation.fromNamespaceAndPath("thaumcraft", name)));
|
||||
}
|
||||
private static ItemStack ae(String name) {
|
||||
return new ItemStack(net.minecraft.core.registries.BuiltInRegistries.ITEM.get(
|
||||
return new ItemStack(BuiltInRegistries.ITEM.get(
|
||||
ResourceLocation.fromNamespaceAndPath("ae2", name)));
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class PseudoResearchItem extends ResearchItem {
|
||||
} else {
|
||||
// 最终备选:ThE 研究 tab 图标
|
||||
pseudo = new PseudoResearchItem(pseudoKey, category, column, row,
|
||||
ResourceLocation.fromNamespaceAndPath(thaumicenergistics.ThaumicEnergistics.MODID, "textures/research/tab_icon.png"),
|
||||
ResourceLocation.fromNamespaceAndPath(ThaumicEnergistics.MODID, "textures/research/tab_icon.png"),
|
||||
realKey, realCategory);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ItemEssentiaCell extends Item implements IBasicCellItem {
|
||||
private final int totalTypes;
|
||||
private final double idleDrain;
|
||||
|
||||
public ItemEssentiaCell(Item.Properties props, String tier, int kilobytes, int bytesPerType, int totalTypes, double idleDrain) {
|
||||
public ItemEssentiaCell(Properties props, String tier, int kilobytes, int bytesPerType, int totalTypes, double idleDrain) {
|
||||
super(props.stacksTo(1));
|
||||
this.tier = tier;
|
||||
this.totalBytes = kilobytes * 1024;
|
||||
@@ -77,7 +77,7 @@ public class ItemEssentiaCell extends Item implements IBasicCellItem {
|
||||
|
||||
@Override
|
||||
public boolean isBlackListed(ItemStack cellItem, appeng.api.stacks.AEKey requestedAddition) {
|
||||
if (requestedAddition instanceof thaumicenergistics.common.integration.appeng.AEssentiaKey) {
|
||||
if (requestedAddition instanceof AEssentiaKey) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -208,19 +208,19 @@ public class ItemEssentiaCell extends Item implements IBasicCellItem {
|
||||
}
|
||||
|
||||
// ===== 工厂方法 =====
|
||||
public static ItemEssentiaCell create1k(Item.Properties props) {
|
||||
public static ItemEssentiaCell create1k(Properties props) {
|
||||
return new ItemEssentiaCell(props, "1k", 1, 8, ThEConfig.CELL_MAX_TYPES, 0.5);
|
||||
}
|
||||
public static ItemEssentiaCell create4k(Item.Properties props) {
|
||||
public static ItemEssentiaCell create4k(Properties props) {
|
||||
return new ItemEssentiaCell(props, "4k", 4, 8, ThEConfig.CELL_MAX_TYPES, 1.0);
|
||||
}
|
||||
public static ItemEssentiaCell create16k(Item.Properties props) {
|
||||
public static ItemEssentiaCell create16k(Properties props) {
|
||||
return new ItemEssentiaCell(props, "16k", 16, 8, ThEConfig.CELL_MAX_TYPES, 1.5);
|
||||
}
|
||||
public static ItemEssentiaCell create64k(Item.Properties props) {
|
||||
public static ItemEssentiaCell create64k(Properties props) {
|
||||
return new ItemEssentiaCell(props, "64k", 64, 8, ThEConfig.CELL_MAX_TYPES, 2.0);
|
||||
}
|
||||
public static ItemEssentiaCell createCreative(Item.Properties props) {
|
||||
public static ItemEssentiaCell createCreative(Properties props) {
|
||||
return new ItemEssentiaCell(props, "creative", Integer.MAX_VALUE / 1024, 8, 63, 0.0);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class ItemGolemWirelessBackpack extends Item {
|
||||
public static final IGridLinkableHandler LINKABLE_HANDLER = new LinkableHandler();
|
||||
|
||||
public ItemGolemWirelessBackpack() {
|
||||
super(new Item.Properties().stacksTo(1).durability(0));
|
||||
super(new Properties().stacksTo(1).durability(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
public class PartItemEssentiaExportBus extends Item implements IPartItem<EssentiaExportBusPart> {
|
||||
|
||||
public static PartItemEssentiaExportBus create() {
|
||||
return new PartItemEssentiaExportBus(new Item.Properties().stacksTo(64));
|
||||
return new PartItemEssentiaExportBus(new Properties().stacksTo(64));
|
||||
}
|
||||
|
||||
public PartItemEssentiaExportBus(Properties properties) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
public class PartItemEssentiaImportBus extends Item implements IPartItem<EssentiaImportBusPart> {
|
||||
|
||||
public static PartItemEssentiaImportBus create() {
|
||||
return new PartItemEssentiaImportBus(new Item.Properties().stacksTo(64));
|
||||
return new PartItemEssentiaImportBus(new Properties().stacksTo(64));
|
||||
}
|
||||
|
||||
public PartItemEssentiaImportBus(Properties properties) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
public class PartItemEssentiaStorageBus extends Item implements IPartItem<EssentiaStorageBusPart> {
|
||||
|
||||
public static PartItemEssentiaStorageBus create() {
|
||||
return new PartItemEssentiaStorageBus(new Item.Properties().stacksTo(64));
|
||||
return new PartItemEssentiaStorageBus(new Properties().stacksTo(64));
|
||||
}
|
||||
|
||||
public PartItemEssentiaStorageBus(Properties properties) {
|
||||
|
||||
+45
@@ -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<DistillationEncoderSetSourceC2SPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("de_set_source"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, DistillationEncoderSetSourceC2SPacket> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, DistillationEncoderSetSourceC2SPacket::containerId,
|
||||
ItemStack.OPTIONAL_STREAM_CODEC, DistillationEncoderSetSourceC2SPacket::stack,
|
||||
DistillationEncoderSetSourceC2SPacket::new
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -19,8 +19,8 @@ public class TransportPermissions implements IThETransportPermissions {
|
||||
@Override
|
||||
public <T extends BlockEntity> boolean addTileToInject(Class<T> c, int cap) { return tileInject.putIfAbsent(c, cap) == null; }
|
||||
@Override
|
||||
public java.util.OptionalLong getCapacityForTile(Class<? extends BlockEntity> c) {
|
||||
Integer v = tileExtract.get(c); return v != null ? java.util.OptionalLong.of(v) : java.util.OptionalLong.empty();
|
||||
public OptionalLong getCapacityForTile(Class<? extends BlockEntity> 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()); }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ public class TileEssentiaProvider extends TileProviderBase implements IEssentiaT
|
||||
}
|
||||
|
||||
/** getEssentiaList 短时缓存:TC 管道 suction 每 tick 调 getEssentiaType,避免每 tick 全量遍历。 */
|
||||
private List<IAspectStack> cachedEssentiaList = java.util.List.of();
|
||||
private List<IAspectStack> cachedEssentiaList = List.of();
|
||||
private int essentiaListCacheTimer = 0;
|
||||
private static final int ESSENTIA_LIST_CACHE_TICKS = 20;
|
||||
|
||||
private List<thaumicenergistics.api.storage.IAspectStack> getCachedEssentiaList() {
|
||||
private List<IAspectStack> 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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -45,11 +45,11 @@ public class ArcaneCraftingTransferInfo
|
||||
|
||||
@Override
|
||||
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return menu.getSlots(appeng.menu.SlotSemantics.CRAFTING_GRID);
|
||||
return menu.getSlots(SlotSemantics.CRAFTING_GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getInventorySlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return menu.getSlots(appeng.menu.SlotSemantics.PLAYER_INVENTORY);
|
||||
return menu.getSlots(SlotSemantics.PLAYER_INVENTORY);
|
||||
}
|
||||
}
|
||||
|
||||
+55
@@ -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<GuiDistillationPatternEncoder> {
|
||||
|
||||
/** 与 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 <I> List<Target<I>> getTargetsTyped(GuiDistillationPatternEncoder gui, ITypedIngredient<I> ingredient, boolean doStart) {
|
||||
List<Target<I>> 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() {
|
||||
}
|
||||
}
|
||||
+93
@@ -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<ContainerKnowledgeInscriber, JeiArcaneRecipe> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final IRecipeTransferHandlerHelper helper;
|
||||
|
||||
public KnowledgeInscriberArcaneRecipeTransfer(IRecipeTransferHandlerHelper helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerKnowledgeInscriber> getContainerClass() {
|
||||
return ContainerKnowledgeInscriber.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerKnowledgeInscriber>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.KNOWLEDGE_INSCRIBER.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeType<JeiArcaneRecipe> 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<ItemStack> template = NonNullList.withSize(9, ItemStack.EMPTY);
|
||||
List<List<ItemStack>> inputs = recipe.inputs();
|
||||
for (int i = 0; i < template.size() && i < inputs.size(); i++) {
|
||||
List<ItemStack> variants = inputs.get(i);
|
||||
if (variants != null && !variants.isEmpty()) {
|
||||
template.set(i, variants.get(0).copyWithCount(1));
|
||||
}
|
||||
}
|
||||
|
||||
// 按 ghost 槽顺序填入 + C2S 同步服务端 + 客户端刷新产物
|
||||
List<GhostSlot> 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;
|
||||
}
|
||||
}
|
||||
+65
@@ -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<GuiKnowledgeInscriber> {
|
||||
|
||||
@Override
|
||||
public <I> List<Target<I>> getTargetsTyped(GuiKnowledgeInscriber gui, ITypedIngredient<I> ingredient, boolean doStart) {
|
||||
List<Target<I>> 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() {
|
||||
}
|
||||
}
|
||||
@@ -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<ContainerKnowledgeInscriber, JeiArcaneRecipe> {
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerKnowledgeInscriber> getContainerClass() {
|
||||
return ContainerKnowledgeInscriber.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerKnowledgeInscriber>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.KNOWLEDGE_INSCRIBER.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeType<JeiArcaneRecipe> getRecipeType() {
|
||||
return ThaumcraftJeiPlugin.ARCANE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getRecipeSlots(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) {
|
||||
List<Slot> slots = new ArrayList<>();
|
||||
for (Slot slot : menu.slots) {
|
||||
if (slot instanceof GhostSlot) {
|
||||
slots.add(slot);
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getInventorySlots(ContainerKnowledgeInscriber menu, JeiArcaneRecipe recipe) {
|
||||
// 玩家背包槽先于机器槽加入(0-35)
|
||||
return menu.slots.subList(0, 36);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"bottom": 140
|
||||
},
|
||||
"STORAGE": {
|
||||
"left": 136,
|
||||
"left": 134,
|
||||
"bottom": 163
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user