Files
thaumicenergistics-neo/src/main/java/thaumicenergistics/common/container/slot/ArcaneCraftingResultSlot.java
T
2026-08-17 15:15:04 +08:00

270 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package thaumicenergistics.common.container.slot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import appeng.api.inventories.InternalInventory;
import appeng.api.networking.energy.IEnergySource;
import appeng.api.networking.security.IActionSource;
import appeng.api.storage.MEStorage;
import appeng.helpers.ICraftingGridMenu;
import appeng.helpers.InventoryAction;
import appeng.menu.slot.CraftingTermSlot;
import appeng.util.inv.PlayerInternalInventory;
import thaumcraft.api.aspects.Aspect;
import thaumcraft.api.aspects.AspectList;
import thaumcraft.common.items.TCFunctionalItems;
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
/**
* 奥术合成结果槽:继承 AE2 CraftingTermSlot 使客户端识别(shift/右键/space 合成自动生效),
* 并保留 vis 消耗检查(mayPickup)与取走时 vis 扣除 + 自动补货(onTake)。
*/
public class ArcaneCraftingResultSlot extends CraftingTermSlot {
/** TC4 内部使用 centivis,1 vis = 100 centivis */
private static final int CENTIVIS = 100;
private AspectList requiredAspects = null;
private ItemStack wandStack = ItemStack.EMPTY;
private ContainerArcaneCraftingTerminal container = null;
private final MEStorage storage;
private final IEnergySource energySrc;
private final IActionSource mySrc;
private final InternalInventory craftingGrid;
public ArcaneCraftingResultSlot(Player player, ContainerArcaneCraftingTerminal container,
InternalInventory craftingGridInv, InternalInventory secondMatrix,
MEStorage storage, IEnergySource energySrc, IActionSource mySrc) {
super(player, mySrc, energySrc, storage, craftingGridInv, secondMatrix, (ICraftingGridMenu) container);
this.container = container;
this.storage = storage;
this.energySrc = energySrc;
this.mySrc = mySrc;
this.craftingGrid = craftingGridInv;
}
public void setRequiredAspects(AspectList aspects) {
this.requiredAspects = aspects;
}
public void setWandStack(ItemStack wandStack) {
this.wandStack = wandStack;
}
@Override
public boolean mayPickup(Player player) {
// AE2 原版:结果槽不可 vanilla 点按取走,所有取走走 doClick(含 vis 检查)
return false;
}
@Override
public void doClick(InventoryAction action, Player who) {
// vis 检查:奥术配方需法杖有足够 vis,否则不合成
if (requiredAspects != null && !requiredAspects.isEmpty()) {
if (wandStack.isEmpty()) {
return;
}
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) {
return;
}
AspectList centivisCost = new AspectList();
for (Aspect aspect : requiredAspects.aspects()) {
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
}
if (!wand.consumeVisCost(wandStack, who, centivisCost, false, true)) {
return;
}
}
// 奥术配方(非 vanilla CRAFTING)不走 AE2 craftItem(findRecipe 只认 vanilla)
// 走自定义合成:从合成格读配方 → 产物 + 消耗材料 + 从网络补货
if (container != null && container.hasValidRecipe) {
doArcaneCraft(action, who);
return;
}
// vanilla 配方:严格 AE2(从网络提取材料填合成格)
super.doClick(action, who);
// 消耗 vis(实际扣除)
if (requiredAspects != null && !requiredAspects.isEmpty() && !wandStack.isEmpty()) {
if (wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand) {
AspectList centivisCost = new AspectList();
for (Aspect aspect : requiredAspects.aspects()) {
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
}
wand.consumeVisCost(wandStack, who, centivisCost, true, true);
}
}
}
/** 奥术配方合成:从合成格读 Thaumcraft 奥术配方 → 产物 + 消耗材料 + 从网络补货。 */
private void doArcaneCraft(InventoryAction action, Player who) {
if (this.getItem().isEmpty() || isRemote()) {
return;
}
var grid = craftingGrid;
var level = who.level();
if (level.isClientSide()) return;
// 用 Thaumcraft 奥术配方匹配(取配方对象以支持 retainIngredient 保留原料)
var craftContainer = grid.toContainer();
thaumcraft.api.crafting.IArcaneRecipe arcaneRecipe = thaumcraft.common.lib.crafting.ThaumcraftCraftingManager
.findMatchingArcaneRecipeEntry(craftContainer, who);
if (arcaneRecipe == null) {
return;
}
ItemStack arcaneResult = arcaneRecipe.getCraftingResult(craftContainer);
if (arcaneResult.isEmpty()) {
return;
}
// 保留原料(retainIngredient,如元始珍珠):getRemainingItems 返回非空栈的槽位不消耗。
// 对应 TC 奥术工作台行为——珍珠作为"催化"留在合成格,仅消耗构材与虚空金属锭。
net.minecraft.core.NonNullList<net.minecraft.world.item.ItemStack> remainders =
arcaneRecipe.getRemainingItems(craftContainer);
// shift=整组到背包、普通=1 个到手
int maxTimesToCraft;
InternalInventory target;
if (action == InventoryAction.CRAFT_SHIFT || action == InventoryAction.CRAFT_ALL) {
target = new appeng.util.inv.PlayerInternalInventory(who.getInventory());
maxTimesToCraft = (int) Math.floor((double) arcaneResult.getMaxStackSize() / (double) arcaneResult.getCount());
if (action == InventoryAction.CRAFT_ALL) {
maxTimesToCraft *= net.minecraft.world.entity.player.Inventory.INVENTORY_SIZE;
}
} else {
target = new appeng.util.inv.CarriedItemInventory(getMenu());
maxTimesToCraft = 1;
}
int crafted = 0;
for (int x = 0; x < maxTimesToCraft; x++) {
// 每轮 vis 检查 + 实际扣减(consume=true):法杖 vis 不足则停
if (requiredAspects != null && !requiredAspects.isEmpty()) {
if (wandStack.isEmpty()) break;
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) break;
AspectList centivisCost = new AspectList();
for (Aspect aspect : requiredAspects.aspects()) {
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
}
boolean ok = wand.consumeVisCost(wandStack, who, centivisCost, true, true);
if (!ok) {
break;
}
}
// 目标满或配方失效则停
if (!target.simulateAdd(arcaneResult).isEmpty()) {
break;
}
// 合成前检查奥术配方仍匹配
ItemStack currentResult = thaumcraft.common.lib.crafting.ThaumcraftCraftingManager
.findMatchingArcaneRecipe(grid.toContainer(), who);
if (currentResult.isEmpty()) {
break;
}
// 记录消耗前合成格(首次记录,后续每轮消耗后补货)
var beforeItems = new java.util.HashMap<Integer, ItemStack>();
for (int i = 0; i < grid.size(); i++) {
var s = grid.getStackInSlot(i);
if (!s.isEmpty()) beforeItems.put(i, s.copy());
}
// 消耗合成格材料(每格 1 个);保留原料槽(remainders 非空,如元始珍珠)不消耗
for (int i = 0; i < grid.size(); i++) {
if (!grid.getStackInSlot(i).isEmpty() && remainders.get(i).isEmpty()) {
grid.extractItem(i, 1, false);
}
}
// 从网络补货(对齐 AE2 终端)
if (storage != null && energySrc != null && mySrc != null) {
for (var entry : beforeItems.entrySet()) {
int slot = entry.getKey();
var beforeItem = entry.getValue();
var currentItem = grid.getStackInSlot(slot);
if (currentItem.getCount() < beforeItem.getCount()) {
long needed = beforeItem.getCount() - currentItem.getCount();
var key = appeng.api.stacks.AEItemKey.of(beforeItem);
if (key != null) {
long extracted = appeng.api.storage.StorageHelper.poweredExtraction(
energySrc, storage, key, needed, mySrc);
if (extracted > 0) {
grid.setItemDirect(slot, beforeItem.copyWithCount(
(int) (currentItem.getCount() + extracted)));
}
}
}
}
}
// 产物入目标
var extra = target.addItems(arcaneResult.copy());
if (!extra.isEmpty()) {
appeng.util.Platform.spawnDrops(who.level(), who.blockPosition(), java.util.List.of(extra));
break;
}
crafted++;
}
// 触发容器刷新(重算结果槽)
container.slotsChanged(grid.toContainer());
}
@Override
public void onTake(Player player, ItemStack stack) {
if (container != null) {
container.setCraftingResultBeingTaken(true);
}
try {
// 记录消耗前各合成格物品,取走后从 ME 存储自动补回(AE2 终端行为)
java.util.Map<Integer, ItemStack> beforeItems = new java.util.HashMap<>();
var grid = craftingGrid;
for (int i = 0; i < grid.size(); i++) {
var s = grid.getStackInSlot(i);
if (!s.isEmpty()) beforeItems.put(i, s.copy());
}
super.onTake(player, stack);
if (requiredAspects != null && !requiredAspects.isEmpty() && !wandStack.isEmpty()) {
if (wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand) {
AspectList centivisCost = new AspectList();
for (Aspect aspect : requiredAspects.aspects()) {
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
}
wand.consumeVisCost(wandStack, player, centivisCost, true, true);
}
}
// 自动补货:合成格材料被消耗后从 ME 存储补回
if (!beforeItems.isEmpty()) {
if (storage != null && energySrc != null && mySrc != null) {
for (var entry : beforeItems.entrySet()) {
int slot = entry.getKey();
var beforeItem = entry.getValue();
var currentItem = grid.getStackInSlot(slot);
if (currentItem.getCount() < beforeItem.getCount()) {
long needed = beforeItem.getCount() - currentItem.getCount();
var key = appeng.api.stacks.AEItemKey.of(beforeItem);
if (key != null) {
long extracted = appeng.api.storage.StorageHelper.poweredExtraction(
energySrc, storage, key, needed, mySrc);
if (extracted > 0) {
grid.setItemDirect(slot, beforeItem.copyWithCount(
(int) (currentItem.getCount() + extracted)));
}
}
}
}
}
}
} finally {
if (container != null) {
container.setCraftingResultBeingTaken(false);
}
}
}
}