[全流程完成]

This commit is contained in:
1820982382
2026-08-17 15:15:04 +08:00
parent 286fc2d2c3
commit 13b981a916
47 changed files with 920 additions and 333 deletions
@@ -4,37 +4,44 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import appeng.api.inventories.InternalInventory;
import appeng.menu.slot.AppEngCraftingSlot;
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 thaumcraft.common.lib.crafting.ThaumcraftCraftingManager;
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
/**
* 奥术合成结果槽:在取走合成结果时消耗法杖要素。
* 参考 TC4 1.21.1 ArcaneWorkbenchMenu.onResultTaken + consumeVis。
* 奥术合成结果槽:继承 AE2 CraftingTermSlot 使客户端识别(shift/右键/space 合成自动生效),
* 并保留 vis 消耗检查(mayPickup)与取走时 vis 扣除 + 自动补货(onTake)。
*/
public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
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 Runnable refreshCallback = null;
private ContainerArcaneCraftingTerminal container = null;
private final MEStorage storage;
private final IEnergySource energySrc;
private final IActionSource mySrc;
private final InternalInventory craftingGrid;
public ArcaneCraftingResultSlot(Player player, InternalInventory craftingGridInv) {
super(player, craftingGridInv);
}
public void setContainer(ContainerArcaneCraftingTerminal container) {
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;
}
public void setRefreshCallback(Runnable callback) {
this.refreshCallback = callback;
this.storage = storage;
this.energySrc = energySrc;
this.mySrc = mySrc;
this.craftingGrid = craftingGridInv;
}
public void setRequiredAspects(AspectList aspects) {
@@ -47,40 +54,177 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
@Override
public boolean mayPickup(Player player) {
if (requiredAspects == null || requiredAspects.isEmpty()) {
return super.mayPickup(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;
}
if (wandStack.isEmpty()) return false;
if (!(wandStack.getItem() instanceof TCFunctionalItems.WandCastingItem wand)) {
return false;
// 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;
}
AspectList centivisCost = new AspectList();
for (Aspect aspect : requiredAspects.aspects()) {
centivisCost.add(aspect, requiredAspects.amount(aspect) * CENTIVIS);
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++;
}
return wand.consumeVisCost(wandStack, player, centivisCost, false, true);
// 触发容器刷新(重算结果槽)
container.slotsChanged(grid.toContainer());
}
@Override
public void onTake(Player player, ItemStack stack) {
// 记录消耗前各合成格中的物品类型,用于消耗后从 ME 网络补充
java.util.Map<Integer, ItemStack> beforeItems = new java.util.HashMap<>();
if (container != null) {
var grid = container.getCraftingMatrix();
for (int i = 0; i < grid.size(); i++) {
var s = grid.getStackInSlot(i);
if (!s.isEmpty()) {
beforeItems.put(i, s.copy());
}
}
}
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()) {
@@ -93,28 +237,22 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
}
}
// 从 ME 网络自动补充消耗掉的合成材料
if (container != null && !beforeItems.isEmpty()) {
var grid = container.getCraftingMatrix();
var node = container.getGridNode();
if (node != null && node.getGrid() != null) {
var storage = node.getGrid().getStorageService().getInventory();
var energy = container.getEnergySource();
var actionSource = container.getActionSource();
// 自动补货:合成格材料被消耗后从 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(
energy, storage, key, needed, actionSource);
energySrc, storage, key, needed, mySrc);
if (extracted > 0) {
var toPlace = beforeItem.copyWithCount((int) (currentItem.getCount() + extracted));
grid.setItemDirect(slot, toPlace);
grid.setItemDirect(slot, beforeItem.copyWithCount(
(int) (currentItem.getCount() + extracted)));
}
}
}
@@ -126,8 +264,6 @@ public class ArcaneCraftingResultSlot extends AppEngCraftingSlot {
container.setCraftingResultBeingTaken(false);
}
}
if (refreshCallback != null) {
refreshCallback.run();
}
}
}