提交
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.Inventory;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
|
import net.minecraft.world.inventory.ContainerData;
|
||||||
import net.minecraft.world.inventory.Slot;
|
import net.minecraft.world.inventory.Slot;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.neoforged.neoforge.items.ItemStackHandler;
|
import net.neoforged.neoforge.items.ItemStackHandler;
|
||||||
import net.neoforged.neoforge.items.SlotItemHandler;
|
import net.neoforged.neoforge.items.SlotItemHandler;
|
||||||
import thaumcraft.api.IVisDiscountGear;
|
import thaumcraft.api.IVisDiscountGear;
|
||||||
import thaumcraft.api.IWarpingGear;
|
import thaumcraft.api.IWarpingGear;
|
||||||
|
import thaumcraft.api.aspects.Aspect;
|
||||||
|
import thaumicenergistics.ThaumicEnergistics;
|
||||||
import thaumicenergistics.common.tiles.TileArcaneAssembler;
|
import thaumicenergistics.common.tiles.TileArcaneAssembler;
|
||||||
import thaumicenergistics.init.ModMenuTypes;
|
import thaumicenergistics.init.ModMenuTypes;
|
||||||
|
|
||||||
@@ -81,6 +84,47 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
|||||||
|
|
||||||
public final TileArcaneAssembler assembler;
|
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) */
|
/** 服务端构造(由 Block#getMenuProvider 调用,不带 tile) */
|
||||||
public ContainerArcaneAssembler(int id, Inventory inv) {
|
public ContainerArcaneAssembler(int id, Inventory inv) {
|
||||||
this(id, inv, (TileArcaneAssembler) null);
|
this(id, inv, (TileArcaneAssembler) null);
|
||||||
@@ -90,6 +134,13 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
|||||||
public ContainerArcaneAssembler(int id, Inventory inv, TileArcaneAssembler tile) {
|
public ContainerArcaneAssembler(int id, Inventory inv, TileArcaneAssembler tile) {
|
||||||
super(ModMenuTypes.ARCANE_ASSEMBLER.get(), id);
|
super(ModMenuTypes.ARCANE_ASSEMBLER.get(), id);
|
||||||
this.assembler = tile;
|
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行 =====
|
// ===== 1. 玩家背包 3行 =====
|
||||||
for (int r = 0; r < 3; r++)
|
for (int r = 0; r < 3; r++)
|
||||||
@@ -166,7 +217,16 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
|
|||||||
|
|
||||||
/** 客户端构造(IMenuTypeExtension 反射调用) */
|
/** 客户端构造(IMenuTypeExtension 反射调用) */
|
||||||
public ContainerArcaneAssembler(int id, Inventory inv, RegistryFriendlyByteBuf buf) {
|
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() {
|
private ItemStackHandler wrapMachineInventory() {
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ public class ContainerArcaneCraftingTerminal extends MEStorageMenu implements IC
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mayPlace(ItemStack stack) {
|
||||||
|
//只允许 TCFunctionalItems.WandCastingItem 类型的物品放入
|
||||||
|
return stack.getItem() instanceof TCFunctionalItems.WandCastingItem;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChanged() {
|
public void setChanged() {
|
||||||
super.setChanged();
|
super.setChanged();
|
||||||
|
|||||||
+32
-18
@@ -5,6 +5,7 @@ import appeng.api.stacks.AEItemKey;
|
|||||||
import appeng.api.stacks.GenericStack;
|
import appeng.api.stacks.GenericStack;
|
||||||
import appeng.core.definitions.AEItems;
|
import appeng.core.definitions.AEItems;
|
||||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
@@ -21,6 +22,7 @@ import thaumcraft.common.research.ThaumometerScanManager;
|
|||||||
import thaumicenergistics.ThaumicEnergistics;
|
import thaumicenergistics.ThaumicEnergistics;
|
||||||
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
|
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
|
||||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||||
|
import thaumicenergistics.common.network.DistillationEncoderSetSourceC2SPacket;
|
||||||
import thaumicenergistics.common.tiles.TileDistillationPatternEncoder;
|
import thaumicenergistics.common.tiles.TileDistillationPatternEncoder;
|
||||||
import thaumicenergistics.init.ModMenuTypes;
|
import thaumicenergistics.init.ModMenuTypes;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -104,14 +106,18 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (encoder != null) {
|
if (encoder != null) {
|
||||||
// 源物品槽:绑定 Tile 槽 0(持久化到方块 NBT,关闭 GUI 不丢失)
|
// 源物品槽:ghost 模板(背包拖入不消耗——实际物品还在手里;空手点击清空;JEI 拖拽设置模板)
|
||||||
addSlot(new SlotItemHandler(encoder.inventory, TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, SLOT_SOURCE_X, SLOT_SOURCE_Y) {
|
addSlot(new SlotItemHandler(encoder.inventory, TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, SLOT_SOURCE_X, SLOT_SOURCE_Y) {
|
||||||
@Override
|
@Override
|
||||||
public int getMaxStackSize() { return 1; }
|
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) {
|
addSlot(new SlotItemHandler(encoder.inventory, TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS, SLOT_BLANK_X, SLOT_BLANK_Y) {
|
||||||
@Override
|
@Override
|
||||||
public boolean mayPlace(ItemStack s) { return true; }
|
public boolean mayPlace(ItemStack s) { return AEItems.BLANK_PATTERN.is(s); }
|
||||||
@Override
|
@Override
|
||||||
public int getMaxStackSize() { return 64; }
|
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) {
|
addSlot(new SlotItemHandler(dummy, TileDistillationPatternEncoder.SLOT_SOURCE_ITEM, SLOT_SOURCE_X, SLOT_SOURCE_Y) {
|
||||||
@Override
|
@Override
|
||||||
public int getMaxStackSize() { return 1; }
|
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) {
|
addSlot(new SlotItemHandler(dummy, TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS, SLOT_BLANK_X, SLOT_BLANK_Y) {
|
||||||
@Override
|
@Override
|
||||||
public boolean mayPlace(ItemStack s) { return true; }
|
public boolean mayPlace(ItemStack s) { return AEItems.BLANK_PATTERN.is(s); }
|
||||||
@Override
|
@Override
|
||||||
public int getMaxStackSize() { return 64; }
|
public int getMaxStackSize() { return 64; }
|
||||||
});
|
});
|
||||||
@@ -150,6 +160,14 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
return ItemStack.EMPTY;
|
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} 重新解析,
|
* 源物品的 aspects:源物品变化(含 NBT/扫描数据)时才调用 {@code getObjectAspects} 重新解析,
|
||||||
* 否则返回缓存——每帧渲染调用仅做 O(1) 物品比较,不频繁请求 TC。
|
* 否则返回缓存——每帧渲染调用仅做 O(1) 物品比较,不频繁请求 TC。
|
||||||
@@ -235,13 +253,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
ItemStack source = getSourceItem();
|
ItemStack source = getSourceItem();
|
||||||
ItemStack blankSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS);
|
ItemStack blankSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_BLANK_PATTERNS);
|
||||||
ItemStack encodedSlot = encoder.inventory.getStackInSlot(TileDistillationPatternEncoder.SLOT_ENCODED_PATTERN);
|
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;
|
if (selected == null) return;
|
||||||
|
|
||||||
@@ -251,8 +262,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
|
|
||||||
// 验证必须是 AE2 空白样板,否则不消耗不编码(日志定位 is() 运行时行为)
|
// 验证必须是 AE2 空白样板,否则不消耗不编码(日志定位 is() 运行时行为)
|
||||||
if (!AEItems.BLANK_PATTERN.is(blankSlot)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,11 +297,18 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
Slot slot = slots.get(slotId);
|
Slot slot = slots.get(slotId);
|
||||||
|
|
||||||
if (slotId == SOURCE_SLOT) {
|
if (slotId == SOURCE_SLOT) {
|
||||||
// 标准拖放处理(SlotItemHandler 已限制槽内 1 个),刷新 aspects
|
// ghost 模板语义:手持物品→设模板(不消耗,实际物品留在手里);空手→清空
|
||||||
super.clicked(slotId, dragType, clickType, player);
|
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();
|
updateAspects();
|
||||||
ThaumicEnergistics.LOG.info("[DE] source slot clicked: item={}, count={}, aspects={}",
|
|
||||||
getSourceItem().getItem().getDescriptionId(), getSourceItem().getCount(), getSourceAspects().length);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,8 +316,6 @@ public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
|
|||||||
int aspectIdx = slotId - ASPECT_SLOT_START;
|
int aspectIdx = slotId - ASPECT_SLOT_START;
|
||||||
// 始终记录用户选择(越界由 getSelectedAspect 校验),避免因 cachedAspects 状态导致服务端选择丢失
|
// 始终记录用户选择(越界由 getSelectedAspect 校验),避免因 cachedAspects 状态导致服务端选择丢失
|
||||||
selectedAspectIndex = aspectIdx;
|
selectedAspectIndex = aspectIdx;
|
||||||
ThaumicEnergistics.LOG.info("[DE] aspect clicked: idx={}, cached={}, selected={}",
|
|
||||||
aspectIdx, cachedAspects.length, selectedAspectIndex);
|
|
||||||
// selected 槽留空,图标由 GUI 绘制
|
// selected 槽留空,图标由 GUI 绘制
|
||||||
aspectInventory.setStackInSlot(SELECTED_SLOT - ASPECT_SLOT_START, ItemStack.EMPTY);
|
aspectInventory.setStackInSlot(SELECTED_SLOT - ASPECT_SLOT_START, ItemStack.EMPTY);
|
||||||
return;
|
return;
|
||||||
|
|||||||
+45
-23
@@ -105,8 +105,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
super(ModMenuTypes.KNOWLEDGE_INSCRIBER.get(), id);
|
super(ModMenuTypes.KNOWLEDGE_INSCRIBER.get(), id);
|
||||||
this.tile = tile;
|
this.tile = tile;
|
||||||
this.playerInv = inv;
|
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 r = 0; r < 3; r++)
|
||||||
for (int c = 0; c < 9; c++)
|
for (int c = 0; c < 9; c++)
|
||||||
@@ -240,14 +238,11 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activeRecipe = findArcaneRecipe();
|
activeRecipe = findArcaneRecipe();
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: activeRecipe={}", activeRecipe);
|
|
||||||
|
|
||||||
if (activeRecipe != null) {
|
if (activeRecipe != null) {
|
||||||
ItemStack output = activeRecipe.getCraftingResult(craftingContainer);
|
ItemStack output = activeRecipe.getCraftingResult(craftingContainer);
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: output={}", output.isEmpty() ? "EMPTY" : output.getItem().toString());
|
|
||||||
resultInventory.setStackInSlot(0, output);
|
resultInventory.setStackInSlot(0, output);
|
||||||
} else {
|
} else {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] updateRecipe: no recipe matched → EMPTY");
|
|
||||||
resultInventory.setStackInSlot(0, ItemStack.EMPTY);
|
resultInventory.setStackInSlot(0, ItemStack.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,28 +252,21 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
private IArcaneRecipe findArcaneRecipe() {
|
private IArcaneRecipe findArcaneRecipe() {
|
||||||
Level level = playerInv.player.level();
|
Level level = playerInv.player.level();
|
||||||
if (level == null) {
|
if (level == null) {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: level is null");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var recipes = ThaumcraftApi.getCraftingRecipes();
|
var recipes = ThaumcraftApi.getCraftingRecipes();
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: total recipes={}", recipes.size());
|
|
||||||
int arcaneCount = 0;
|
int arcaneCount = 0;
|
||||||
for (Object recipe : recipes) {
|
for (Object recipe : recipes) {
|
||||||
if (recipe instanceof IArcaneRecipe arcaneRecipe) {
|
if (recipe instanceof IArcaneRecipe arcaneRecipe) {
|
||||||
arcaneCount++;
|
arcaneCount++;
|
||||||
try {
|
try {
|
||||||
if (gridMatches(arcaneRecipe, craftingContainer)) {
|
if (gridMatches(arcaneRecipe, craftingContainer)) {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] findArcaneRecipe: MATCH FOUND! recipe={}",
|
|
||||||
arcaneRecipe);
|
|
||||||
return arcaneRecipe;
|
return arcaneRecipe;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +347,8 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
|
|
||||||
private void sendSaveState() {
|
private void sendSaveState() {
|
||||||
if (tile != null && tile.getLevel() != null && !tile.getLevel().isClientSide) {
|
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) {
|
public void clicked(int slotId, int dragType, ClickType clickType, Player player) {
|
||||||
if (slotId >= 0 && slotId < slots.size()) {
|
if (slotId >= 0 && slotId < slots.size()) {
|
||||||
Slot slot = slots.get(slotId);
|
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) {
|
if (slot instanceof GhostSlot ghostSlot) {
|
||||||
int gridIndex = ghostSlot.index;
|
int gridIndex = ghostSlot.index;
|
||||||
ItemStack carried = getCarried();
|
ItemStack carried = getCarried();
|
||||||
ItemStack newStack = carried.isEmpty()
|
ItemStack newStack = carried.isEmpty()
|
||||||
? ItemStack.EMPTY
|
? ItemStack.EMPTY
|
||||||
: carried.copyWithCount(1);
|
: 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);
|
ghostSlot.set(newStack);
|
||||||
|
|
||||||
if (player.level().isClientSide) {
|
if (player.level().isClientSide) {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] CLIENT branch → onCraftingChangedClient + C2S");
|
|
||||||
onCraftingChangedClient();
|
onCraftingChangedClient();
|
||||||
PacketDistributor.sendToServer(
|
PacketDistributor.sendToServer(
|
||||||
new KnowledgeInscriberGhostSlotPacket(containerId, gridIndex, newStack));
|
new KnowledgeInscriberGhostSlotPacket(containerId, gridIndex, newStack));
|
||||||
} else {
|
} else {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] SERVER branch → onCraftingChanged");
|
|
||||||
onCraftingChanged();
|
onCraftingChanged();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -453,8 +449,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
public void setGhostSlotOnServer(int slotIndex, ItemStack stack) {
|
public void setGhostSlotOnServer(int slotIndex, ItemStack stack) {
|
||||||
if (slotIndex < 0 || slotIndex >= CRAFTING_GRID_SIZE) return;
|
if (slotIndex < 0 || slotIndex >= CRAFTING_GRID_SIZE) return;
|
||||||
ItemStack s = stack == null ? ItemStack.EMPTY : stack.copy();
|
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);
|
craftingContainer.setItem(slotIndex, s);
|
||||||
int slotId = 37 + MAXIMUM_PATTERNS + slotIndex;
|
int slotId = 37 + MAXIMUM_PATTERNS + slotIndex;
|
||||||
if (slotId < slots.size() && slots.get(slotId) instanceof GhostSlot) {
|
if (slotId < slots.size() && slots.get(slotId) instanceof GhostSlot) {
|
||||||
@@ -463,8 +457,37 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
onCraftingChanged();
|
onCraftingChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCraftingChangedClient() {
|
/** GhostSlot 的网格索引(0-8)。ghostSlot.index 是菜单槽序号(37+MAXIMUM_PATTERNS+idx),需换算。 */
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] onCraftingChangedClient");
|
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++) {
|
for (int i = 0; i < CRAFTING_GRID_SIZE; i++) {
|
||||||
craftingInventory.setStackInSlot(i, craftingContainer.getItem(i).copy());
|
craftingInventory.setStackInSlot(i, craftingContainer.getItem(i).copy());
|
||||||
}
|
}
|
||||||
@@ -472,7 +495,6 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCraftingChanged() {
|
private void onCraftingChanged() {
|
||||||
ThaumicEnergistics.LOG.info("[KLGE] onCraftingChanged");
|
|
||||||
for (int i = 0; i < CRAFTING_GRID_SIZE; i++) {
|
for (int i = 0; i < CRAFTING_GRID_SIZE; i++) {
|
||||||
craftingInventory.setStackInSlot(i, craftingContainer.getItem(i).copy());
|
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.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import thaumcraft.api.aspects.Aspect;
|
import net.minecraft.world.inventory.ContainerData;
|
||||||
import thaumicenergistics.ThaumicEnergistics;
|
import thaumicenergistics.ThaumicEnergistics;
|
||||||
import thaumicenergistics.common.tiles.TileArcaneAssembler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 奥术装配器 GUI。
|
* 奥术装配器 GUI。
|
||||||
@@ -27,17 +26,15 @@ public class GuiArcaneAssembler extends AbstractContainerScreen<ContainerArcaneA
|
|||||||
ThaumicEnergistics.MODID, "textures/gui/arcane_assembler.png");
|
ThaumicEnergistics.MODID, "textures/gui/arcane_assembler.png");
|
||||||
|
|
||||||
// ===== 第4块 Vis条 =====
|
// ===== 第4块 Vis条 =====
|
||||||
private static final int VIS_BAR_X = 42;
|
/** 纹理中空槽(暗色背景)的 Y 坐标 = 进度条在 GUI 中的显示位置 */
|
||||||
private static final int VIS_BAR_Y = 202;
|
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_W = 4;
|
||||||
private static final int VIS_BAR_H = 16;
|
private static final int VIS_BAR_H = 16;
|
||||||
private static final int VIS_BAR_STEP = 18;
|
private static final int VIS_BAR_STEP = 18;
|
||||||
|
/** 贴图规范起始 X = 41(步长18:41,59,77,95,113,131,149) */
|
||||||
// ===== 6原质 =====
|
private static final int VIS_BAR_X = 41;
|
||||||
private static final Aspect[] VIS_ASPECTS = {
|
|
||||||
Aspect.AIR, Aspect.FIRE, Aspect.WATER,
|
|
||||||
Aspect.EARTH, Aspect.ORDER, Aspect.ENTROPY
|
|
||||||
};
|
|
||||||
|
|
||||||
public GuiArcaneAssembler(ContainerArcaneAssembler menu, Inventory inv, Component title) {
|
public GuiArcaneAssembler(ContainerArcaneAssembler menu, Inventory inv, Component title) {
|
||||||
super(menu, inv, title);
|
super(menu, inv, title);
|
||||||
@@ -64,35 +61,48 @@ public class GuiArcaneAssembler extends AbstractContainerScreen<ContainerArcaneA
|
|||||||
// 第3块:右下正方形 68×68
|
// 第3块:右下正方形 68×68
|
||||||
g.blit(TEX, left + 180, top + 129, 180, 129, 68, 68);
|
g.blit(TEX, left + 180, top + 129, 180, 129, 68, 68);
|
||||||
|
|
||||||
// ===== 第4块:Vis条(纹理裁剪方式,仿1.7.10 drawVisBar) =====
|
// ===== 第4块:Vis条(1.7.10 裁剪方式:Y=87 空槽背景 + Y=202 亮条裁剪) =====
|
||||||
TileArcaneAssembler tile = this.menu.assembler;
|
// 数据源:容器同步(menu.visData,服务端每 tick 同步 storedVis/isCrafting/进度——不依赖 BE 网络同步)
|
||||||
|
ContainerData data = this.menu.visData;
|
||||||
final int maxCvis = 187 * 10;
|
final int maxCvis = 187 * 10;
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
int bx = left + VIS_BAR_X + i * VIS_BAR_STEP;
|
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;
|
float ratio = 0f;
|
||||||
if (tile != null) {
|
if (i < 6) {
|
||||||
if (i < 6) {
|
// 1-6格:源质充能比例
|
||||||
int cvis = tile.getStoredVis() != null ? tile.getStoredVis().getAmount(VIS_ASPECTS[i]) : 0;
|
int cvis = data.get(ContainerArcaneAssembler.DATA_AIR + i);
|
||||||
ratio = Math.min(1f, (float) cvis / maxCvis);
|
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 {
|
} 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不在此处,由纹理裁剪提供)
|
// 从纹理 Y=202 裁剪满亮条,按高度百分比叠加
|
||||||
g.fill(bx, by, bx + VIS_BAR_W, by + VIS_BAR_H, 0x77222222);
|
|
||||||
|
|
||||||
// 从纹理Y=202处裁剪满bar,按高度百分比叠加
|
|
||||||
int fillHeight = Math.round(VIS_BAR_H * ratio);
|
int fillHeight = Math.round(VIS_BAR_H * ratio);
|
||||||
if (fillHeight > 0) {
|
if (fillHeight > 0) {
|
||||||
int srcX = VIS_BAR_X + i * VIS_BAR_STEP;
|
int srcX = VIS_BAR_X + i * VIS_BAR_STEP;
|
||||||
int srcY = VIS_BAR_Y + (VIS_BAR_H - fillHeight);
|
int srcY = VIS_FULL_Y + (VIS_BAR_H - fillHeight);
|
||||||
g.blit(TEX, bx, by + (VIS_BAR_H - fillHeight), srcX, srcY, VIS_BAR_W, 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 - 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 + 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);
|
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) {
|
if (cachedAnchorY < 0) {
|
||||||
for (var slot : this.menu.slots) {
|
for (var slot : this.menu.slots) {
|
||||||
// CRAFTING_RESULT 槽在 style JSON 中 bottom:140
|
// CRAFTING_RESULT 槽在 style JSON 中 bottom:140
|
||||||
if (slot instanceof appeng.menu.slot.AppEngCraftingSlot) {
|
if (slot instanceof AppEngCraftingSlot) {
|
||||||
cachedAnchorY = slot.y;
|
cachedAnchorY = slot.y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -383,11 +383,11 @@ public final class RecipeRegistration {
|
|||||||
return new ItemStack(s.get());
|
return new ItemStack(s.get());
|
||||||
}
|
}
|
||||||
private static ItemStack tc(String name) {
|
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)));
|
ResourceLocation.fromNamespaceAndPath("thaumcraft", name)));
|
||||||
}
|
}
|
||||||
private static ItemStack ae(String 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)));
|
ResourceLocation.fromNamespaceAndPath("ae2", name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ public class PseudoResearchItem extends ResearchItem {
|
|||||||
} else {
|
} else {
|
||||||
// 最终备选:ThE 研究 tab 图标
|
// 最终备选:ThE 研究 tab 图标
|
||||||
pseudo = new PseudoResearchItem(pseudoKey, category, column, row,
|
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);
|
realKey, realCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class ItemEssentiaCell extends Item implements IBasicCellItem {
|
|||||||
private final int totalTypes;
|
private final int totalTypes;
|
||||||
private final double idleDrain;
|
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));
|
super(props.stacksTo(1));
|
||||||
this.tier = tier;
|
this.tier = tier;
|
||||||
this.totalBytes = kilobytes * 1024;
|
this.totalBytes = kilobytes * 1024;
|
||||||
@@ -77,7 +77,7 @@ public class ItemEssentiaCell extends Item implements IBasicCellItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBlackListed(ItemStack cellItem, appeng.api.stacks.AEKey requestedAddition) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
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);
|
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);
|
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);
|
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);
|
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);
|
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 static final IGridLinkableHandler LINKABLE_HANDLER = new LinkableHandler();
|
||||||
|
|
||||||
public ItemGolemWirelessBackpack() {
|
public ItemGolemWirelessBackpack() {
|
||||||
super(new Item.Properties().stacksTo(1).durability(0));
|
super(new Properties().stacksTo(1).durability(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
public class PartItemEssentiaExportBus extends Item implements IPartItem<EssentiaExportBusPart> {
|
public class PartItemEssentiaExportBus extends Item implements IPartItem<EssentiaExportBusPart> {
|
||||||
|
|
||||||
public static PartItemEssentiaExportBus create() {
|
public static PartItemEssentiaExportBus create() {
|
||||||
return new PartItemEssentiaExportBus(new Item.Properties().stacksTo(64));
|
return new PartItemEssentiaExportBus(new Properties().stacksTo(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartItemEssentiaExportBus(Properties properties) {
|
public PartItemEssentiaExportBus(Properties properties) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
public class PartItemEssentiaImportBus extends Item implements IPartItem<EssentiaImportBusPart> {
|
public class PartItemEssentiaImportBus extends Item implements IPartItem<EssentiaImportBusPart> {
|
||||||
|
|
||||||
public static PartItemEssentiaImportBus create() {
|
public static PartItemEssentiaImportBus create() {
|
||||||
return new PartItemEssentiaImportBus(new Item.Properties().stacksTo(64));
|
return new PartItemEssentiaImportBus(new Properties().stacksTo(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartItemEssentiaImportBus(Properties properties) {
|
public PartItemEssentiaImportBus(Properties properties) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
public class PartItemEssentiaStorageBus extends Item implements IPartItem<EssentiaStorageBusPart> {
|
public class PartItemEssentiaStorageBus extends Item implements IPartItem<EssentiaStorageBusPart> {
|
||||||
|
|
||||||
public static PartItemEssentiaStorageBus create() {
|
public static PartItemEssentiaStorageBus create() {
|
||||||
return new PartItemEssentiaStorageBus(new Item.Properties().stacksTo(64));
|
return new PartItemEssentiaStorageBus(new Properties().stacksTo(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
public PartItemEssentiaStorageBus(Properties properties) {
|
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)
|
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// DistillationEncoderSetSourceC2SPacket — C2S(蒸馏编码台设置源物品——JEI 拖拽)
|
||||||
|
registrar.playToServer(
|
||||||
|
DistillationEncoderSetSourceC2SPacket.TYPE,
|
||||||
|
DistillationEncoderSetSourceC2SPacket.STREAM_CODEC,
|
||||||
|
(pkt, ctx) -> pkt.handleOnServer(ctx)
|
||||||
|
);
|
||||||
|
|
||||||
// CellWorkbenchC2SPacket — C2S(客户端→服务端,源质元件工作台分区操作)
|
// CellWorkbenchC2SPacket — C2S(客户端→服务端,源质元件工作台分区操作)
|
||||||
registrar.playToServer(
|
registrar.playToServer(
|
||||||
CellWorkbenchC2SPacket.TYPE,
|
CellWorkbenchC2SPacket.TYPE,
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ public class TransportPermissions implements IThETransportPermissions {
|
|||||||
@Override
|
@Override
|
||||||
public <T extends BlockEntity> boolean addTileToInject(Class<T> c, int cap) { return tileInject.putIfAbsent(c, cap) == null; }
|
public <T extends BlockEntity> boolean addTileToInject(Class<T> c, int cap) { return tileInject.putIfAbsent(c, cap) == null; }
|
||||||
@Override
|
@Override
|
||||||
public java.util.OptionalLong getCapacityForTile(Class<? extends BlockEntity> c) {
|
public OptionalLong getCapacityForTile(Class<? extends BlockEntity> c) {
|
||||||
Integer v = tileExtract.get(c); return v != null ? java.util.OptionalLong.of(v) : java.util.OptionalLong.empty();
|
Integer v = tileExtract.get(c); return v != null ? OptionalLong.of(v) : OptionalLong.empty();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean canExtract(BlockEntity tile) { return tile != null && tileExtract.containsKey(tile.getClass()); }
|
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 ACTIVE_POWER = 1.5;
|
||||||
private static final double WARP_POWER_PERCENT = 0.15;
|
private static final double WARP_POWER_PERCENT = 0.15;
|
||||||
private static final int MAX_SPEED_UPGRADES = 4;
|
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 状态。 */
|
/** 每次实际 vis 变化累计达到该值才调用一次 setChanged(约 4×5=20 tick),避免合成期间 chunk 持续处于 unsaved 状态。 */
|
||||||
private static final int SET_CHANGED_INTERVAL = 4;
|
private static final int SET_CHANGED_INTERVAL = 4;
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
super(ModBlockEntities.ARCANE_ASSEMBLER.get(), pos, state);
|
super(ModBlockEntities.ARCANE_ASSEMBLER.get(), pos, state);
|
||||||
|
|
||||||
mainNode = GridHelper.createManagedNode(this, nodeListener)
|
mainNode = GridHelper.createManagedNode(this, nodeListener)
|
||||||
.setVisualRepresentation(thaumicenergistics.init.ModItems.ARCANE_ASSEMBLER_ITEM.get())
|
.setVisualRepresentation(ModItems.ARCANE_ASSEMBLER_ITEM.get())
|
||||||
.setInWorldNode(true)
|
.setInWorldNode(true)
|
||||||
.setTagName("proxy")
|
.setTagName("proxy")
|
||||||
.setFlags(GridFlags.REQUIRE_CHANNEL)
|
.setFlags(GridFlags.REQUIRE_CHANNEL)
|
||||||
@@ -296,13 +296,13 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replenishVis 抽到 vis 时调用:累计变化次数,每 SET_CHANGED_INTERVAL 次才标记一次 chunk dirty。
|
* replenishVis 抽到 vis 时调用:累计变化次数,每 SET_CHANGED_INTERVAL 次才 markForUpdate 一次。
|
||||||
* 降低合成期间持续磁盘写放大;storedVis 最终仍会在合成完成路径(craftingTick)或区块保存时落盘。
|
* markForUpdate = setChanged + sendBlockUpdated(同步 storedVis 到客户端 BE,GUI 进度条依赖客户端 storedVis)。
|
||||||
*/
|
*/
|
||||||
private void maybeMarkVisDirty() {
|
private void maybeMarkVisDirty() {
|
||||||
if (++visSetChangedCounter >= SET_CHANGED_INTERVAL) {
|
if (++visSetChangedCounter >= SET_CHANGED_INTERVAL) {
|
||||||
visSetChangedCounter = 0;
|
visSetChangedCounter = 0;
|
||||||
setChanged();
|
markForUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,6 +360,8 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
if (extracted >= need * 0.9) {
|
if (extracted >= need * 0.9) {
|
||||||
craftTickCounter += ticksSinceLast;
|
craftTickCounter += ticksSinceLast;
|
||||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 推进 {}/{} (抽电 {}/{})", craftTickCounter, ticksPerCraft, extracted, need);
|
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 推进 {}/{} (抽电 {}/{})", craftTickCounter, ticksPerCraft, extracted, need);
|
||||||
|
// 节流同步合成进度到客户端(GUI 第7格进度条动画)
|
||||||
|
maybeMarkVisDirty();
|
||||||
} else {
|
} else {
|
||||||
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 能量不足 (抽到 {}/{}),不推进", extracted, need);
|
ThaumicEnergistics.LOG.debug("[AA] craftingTick: 能量不足 (抽到 {}/{}),不推进", extracted, need);
|
||||||
}
|
}
|
||||||
@@ -375,7 +377,7 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
int current = storedVis.getAmount(aspect);
|
int current = storedVis.getAmount(aspect);
|
||||||
storedVis.reduce(aspect, Math.min(requiredCvis, current));
|
storedVis.reduce(aspect, Math.min(requiredCvis, current));
|
||||||
}
|
}
|
||||||
setChanged();
|
maybeMarkVisDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasEnoughVisForCraft() {
|
private boolean hasEnoughVisForCraft() {
|
||||||
@@ -512,7 +514,7 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PatternContainerGroup getCraftingMachineInfo() {
|
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(
|
return new PatternContainerGroup(
|
||||||
AEItemKey.of(icon),
|
AEItemKey.of(icon),
|
||||||
net.minecraft.network.chat.Component.translatable("block.thaumicenergistics.arcane_assembler"),
|
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,
|
public void onMemoryCardActivate(net.minecraft.world.entity.player.Player player,
|
||||||
appeng.api.implementations.items.IMemoryCard memoryCard,
|
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);
|
Short freq = heldItem.get(appeng.api.ids.AEComponents.EXPORTED_P2P_FREQUENCY);
|
||||||
if (freq != null) {
|
if (freq != null) {
|
||||||
// 卡上带 P2P 频率(从 vis_interface shift+右键复制来的)→ 设为 vis 源
|
// 卡上带 P2P 频率(从 vis_interface shift+右键复制来的)→ 设为 vis 源
|
||||||
@@ -643,6 +645,9 @@ public class TileArcaneAssembler extends ThETileBase
|
|||||||
if (level != null) {
|
if (level != null) {
|
||||||
setChanged();
|
setChanged();
|
||||||
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
|
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 全量遍历。 */
|
/** 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 int essentiaListCacheTimer = 0;
|
||||||
private static final int ESSENTIA_LIST_CACHE_TICKS = 20;
|
private static final int ESSENTIA_LIST_CACHE_TICKS = 20;
|
||||||
|
|
||||||
private List<thaumicenergistics.api.storage.IAspectStack> getCachedEssentiaList() {
|
private List<IAspectStack> getCachedEssentiaList() {
|
||||||
if (essentiaListCacheTimer <= 0) {
|
if (essentiaListCacheTimer <= 0) {
|
||||||
essentiaListCacheTimer = ESSENTIA_LIST_CACHE_TICKS;
|
essentiaListCacheTimer = ESSENTIA_LIST_CACHE_TICKS;
|
||||||
cachedEssentiaList = essentiaGrid != null ? essentiaGrid.getEssentiaList() : java.util.List.of();
|
cachedEssentiaList = essentiaGrid != null ? essentiaGrid.getEssentiaList() : List.of();
|
||||||
}
|
}
|
||||||
return cachedEssentiaList;
|
return cachedEssentiaList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,13 @@ public final class ModBlocks {
|
|||||||
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
}
|
}
|
||||||
if (!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;
|
return net.minecraft.world.ItemInteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
@Override
|
@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);
|
TileArcaneAssembler tile = (TileArcaneAssembler) level.getBlockEntity(pos);
|
||||||
return new SimpleMenuProvider(
|
return new SimpleMenuProvider(
|
||||||
(id, inv, p) -> new thaumicenergistics.common.container.ContainerArcaneAssembler(id, inv, tile),
|
(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;
|
return net.minecraft.world.ItemInteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
@Override
|
@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);
|
TileKnowledgeInscriber tile = (TileKnowledgeInscriber) level.getBlockEntity(pos);
|
||||||
return new SimpleMenuProvider(
|
return new SimpleMenuProvider(
|
||||||
(id, inv, p) -> new thaumicenergistics.common.container.ContainerKnowledgeInscriber(id, inv, tile),
|
(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;
|
return net.minecraft.world.ItemInteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
@Override
|
@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);
|
TileDistillationPatternEncoder tile = (TileDistillationPatternEncoder) level.getBlockEntity(pos);
|
||||||
return new SimpleMenuProvider(
|
return new SimpleMenuProvider(
|
||||||
(id, inv, p) -> new thaumicenergistics.common.container.ContainerDistillationPatternEncoder(id, inv, tile),
|
(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;
|
return net.minecraft.world.ItemInteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
@Override
|
@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);
|
net.minecraft.world.level.block.entity.BlockEntity be = level.getBlockEntity(pos);
|
||||||
if (be instanceof TileEssentiaCellWorkbench workbench) {
|
if (be instanceof TileEssentiaCellWorkbench workbench) {
|
||||||
return new SimpleMenuProvider((id, inv, p) -> new thaumicenergistics.common.container.ContainerEssentiaCellWorkbench(id, inv, workbench),
|
return new SimpleMenuProvider((id, inv, p) -> new thaumicenergistics.common.container.ContainerEssentiaCellWorkbench(id, inv, workbench),
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ public class ArcaneCraftingTransferInfo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||||
return menu.getSlots(appeng.menu.SlotSemantics.CRAFTING_GRID);
|
return menu.getSlots(SlotSemantics.CRAFTING_GRID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Slot> getInventorySlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
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 网络
|
// 注册 IRecipeTransferHandler 让点击 "+" 时走 ME 网络
|
||||||
registration.addRecipeTransferHandler(new ThEUseCraftingRecipeTransfer(registration.getTransferHelper()), CRAFTING);
|
registration.addRecipeTransferHandler(new ThEUseCraftingRecipeTransfer(registration.getTransferHelper()), CRAFTING);
|
||||||
registration.addRecipeTransferHandler(new ThEUseArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE);
|
registration.addRecipeTransferHandler(new ThEUseArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE);
|
||||||
|
|
||||||
|
// 知识记录仪:奥术配方 "+" 一键放置到 3×3 ghost 合成网格
|
||||||
|
registration.addRecipeTransferHandler(new KnowledgeInscriberTransferInfo());
|
||||||
|
registration.addRecipeTransferHandler(
|
||||||
|
new KnowledgeInscriberArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerGuiHandlers(IGuiHandlerRegistration registration) {
|
public void registerGuiHandlers(IGuiHandlerRegistration registration) {
|
||||||
// 源质元件工作台:从 JEI 拖拽要素到分区格添加分区(纯 JEI API,可选功能)
|
// 源质元件工作台:从 JEI 拖拽要素到分区格添加分区(纯 JEI API,可选功能)
|
||||||
registration.addGhostIngredientHandler(GuiEssentiaCellWorkbench.class, new CellWorkbenchGhostIngredientHandler());
|
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
|
"bottom": 140
|
||||||
},
|
},
|
||||||
"STORAGE": {
|
"STORAGE": {
|
||||||
"left": 136,
|
"left": 134,
|
||||||
"bottom": 163
|
"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