[全流程完成]
This commit is contained in:
+187
-51
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user