[全流程完成]
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package thaumicenergistics.common.blocks;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
|
||||
/**
|
||||
* AE2 扳手拆卸处理。AE2 的 WrenchHook 只处理 AEBaseBlockEntity,ThE 方块 BE 不走该路径;
|
||||
* 这里用与 WrenchHook 同级的 RightClickBlock 事件,在 Block.useItemOn 之前拦截扳手右键,
|
||||
* 对 ThE 方块执行拆卸(掉落 + 移除)。
|
||||
*/
|
||||
public final class ThEWrenchHandler {
|
||||
|
||||
private ThEWrenchHandler() {
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
NeoForge.EVENT_BUS.register(ThEWrenchHandler.class);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
|
||||
if (event.isCanceled() || event.getHand() != InteractionHand.MAIN_HAND) {
|
||||
return;
|
||||
}
|
||||
var player = event.getEntity();
|
||||
if (player.isSpectator()) {
|
||||
return;
|
||||
}
|
||||
var stack = player.getItemInHand(event.getHand());
|
||||
if (!player.isShiftKeyDown() || !stack.is(appeng.datagen.providers.tags.ConventionTags.WRENCH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var level = event.getLevel();
|
||||
BlockPos pos = event.getPos();
|
||||
BlockState state = level.getBlockState(pos);
|
||||
// 只处理 ThE 方块(按命名空间判断,兼容未继承 ThETileBase 的工作台等)
|
||||
if (!ThaumicEnergistics.MODID.equals(state.getBlock().builtInRegistryHolder().key().location().getNamespace())) {
|
||||
return;
|
||||
}
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (be == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!level.isClientSide) {
|
||||
// ThE 方块掉落即自身(挖掘走 getDrops 覆写;扳手拆卸直接给方块物品,避免 loot table 为空)
|
||||
player.getInventory().placeItemBackInInventory(new ItemStack(be.getBlockState().getBlock()));
|
||||
level.removeBlock(pos, false);
|
||||
} else {
|
||||
level.playSound(player, pos, SoundEvents.ITEM_FRAME_REMOVE_ITEM, SoundSource.BLOCKS, 0.7F, 1.0F);
|
||||
}
|
||||
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(InteractionResult.sidedSuccess(level.isClientSide));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
|
||||
/**
|
||||
* 客户端→服务端:源质容器存入 ME 网络。
|
||||
* <ul>
|
||||
* <li>手持(carried/主手)源质容器右键 → 源质存入,空容器放回玩家背包(slotIndex=-1)。</li>
|
||||
* <li>Shift+右键玩家背包格 → 源质存入,空容器留在原背包格(slotIndex>=0)。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public record EssentiaTerminalDepositC2SPacket(int containerId, int slotIndex,
|
||||
net.minecraft.world.item.ItemStack sourceStack) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<EssentiaTerminalDepositC2SPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("essentia_terminal_deposit"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, EssentiaTerminalDepositC2SPacket> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, EssentiaTerminalDepositC2SPacket::containerId,
|
||||
ByteBufCodecs.VAR_INT, EssentiaTerminalDepositC2SPacket::slotIndex,
|
||||
net.minecraft.world.item.ItemStack.OPTIONAL_STREAM_CODEC, EssentiaTerminalDepositC2SPacket::sourceStack,
|
||||
EssentiaTerminalDepositC2SPacket::new
|
||||
);
|
||||
|
||||
/** 手持源质物品存入(客户端可能持有在 carried 或主手)。 */
|
||||
public static EssentiaTerminalDepositC2SPacket fromClientStack(int containerId, net.minecraft.world.item.ItemStack stack) {
|
||||
return new EssentiaTerminalDepositC2SPacket(containerId, -1, stack.copy());
|
||||
}
|
||||
|
||||
/** 从玩家背包格存入(Shift+右键背包格)。 */
|
||||
public static EssentiaTerminalDepositC2SPacket fromSlot(int containerId, int slotIndex, net.minecraft.world.item.ItemStack stack) {
|
||||
return new EssentiaTerminalDepositC2SPacket(containerId, slotIndex, stack.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public void handleOnServer(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
// 任意 ME 终端菜单都支持:源质容器右键 → 源质存入网络
|
||||
if (ctx.player().containerMenu.containerId == this.containerId
|
||||
&& ctx.player().containerMenu instanceof appeng.menu.me.common.MEStorageMenu menu
|
||||
&& menu instanceof thaumicenergistics.common.container.ThEEssentiaMenuAccess acc) {
|
||||
var player = menu.getPlayerInventory().player;
|
||||
// 源质物品:优先包携带的 sourceStack(客户端真实来源);否则回退菜单 carried/主手
|
||||
var source = !this.sourceStack.isEmpty() ? this.sourceStack
|
||||
: (!menu.getCarried().isEmpty() ? menu.getCarried()
|
||||
: player.getMainHandItem());
|
||||
if (!source.isEmpty()) {
|
||||
var result = thaumicenergistics.common.container.EssentiaFillHelper.extractContainerContent(
|
||||
acc.thE_getStorage(), acc.thE_getEnergySource(),
|
||||
menu.getActionSource(), source);
|
||||
if (result != null) {
|
||||
if (this.slotIndex >= 0) {
|
||||
// Shift+右键背包格:空容器留在原背包格
|
||||
player.getInventory().setItem(this.slotIndex, result);
|
||||
} else {
|
||||
// 手持:清空 carried,空容器放回玩家背包(不留 carried,防被 AE2 自动放入网络)
|
||||
menu.setCarried(net.minecraft.world.item.ItemStack.EMPTY);
|
||||
if (!result.isEmpty()) {
|
||||
if (!player.getInventory().add(result)) {
|
||||
player.drop(result, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
menu.broadcastChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
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.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
|
||||
/**
|
||||
* 客户端→服务端:源质终端右键源质条目装罐。
|
||||
* 客户端已把 serial 解析为 aspect id(服务端无 client repo,无法解析 serial)。
|
||||
* 携带 sourceStack:罐子可能持于主手(未进入 carried),服务端直接用客户端栈,
|
||||
* 避免只读 menu.getCarried() 为空导致装罐静默失败。
|
||||
*/
|
||||
public record EssentiaTerminalFillC2SPacket(int containerId, ResourceLocation aspectId,
|
||||
ItemStack sourceStack) implements CustomPacketPayload {
|
||||
|
||||
public static final Type<EssentiaTerminalFillC2SPacket> TYPE =
|
||||
new Type<>(ThaumicEnergistics.id("essentia_terminal_fill"));
|
||||
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, EssentiaTerminalFillC2SPacket> STREAM_CODEC =
|
||||
StreamCodec.composite(
|
||||
ByteBufCodecs.VAR_INT, EssentiaTerminalFillC2SPacket::containerId,
|
||||
ResourceLocation.STREAM_CODEC, EssentiaTerminalFillC2SPacket::aspectId,
|
||||
ItemStack.OPTIONAL_STREAM_CODEC, EssentiaTerminalFillC2SPacket::sourceStack,
|
||||
EssentiaTerminalFillC2SPacket::new
|
||||
);
|
||||
|
||||
/** 装罐:sourceStack 为客户端解析出的源质容器(优先 carried,为空回退主手)。 */
|
||||
public static EssentiaTerminalFillC2SPacket create(int containerId, ResourceLocation aspectId, ItemStack stack) {
|
||||
return new EssentiaTerminalFillC2SPacket(containerId, aspectId, stack.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public void handleOnServer(IPayloadContext ctx) {
|
||||
ctx.enqueueWork(() -> {
|
||||
// 通用化:任意 ME 终端菜单都支持右键源质条目装罐(源质终端/奥术合成终端/AE2 ME 终端等)
|
||||
if (ctx.player().containerMenu.containerId == this.containerId
|
||||
&& ctx.player().containerMenu instanceof appeng.menu.me.common.MEStorageMenu menu
|
||||
&& menu instanceof thaumicenergistics.common.container.ThEEssentiaMenuAccess acc) {
|
||||
var player = menu.getPlayerInventory().player;
|
||||
// 源质物品:优先包携带的 sourceStack(客户端真实来源);否则回退菜单 carried/主手
|
||||
var source = !this.sourceStack.isEmpty() ? this.sourceStack
|
||||
: (!menu.getCarried().isEmpty() ? menu.getCarried()
|
||||
: player.getMainHandItem());
|
||||
thaumicenergistics.common.container.EssentiaFillHelper.fillContainerFromGrid(
|
||||
acc.thE_getStorage(), acc.thE_getEnergySource(),
|
||||
menu.getActionSource(), player, source, this.aspectId);
|
||||
menu.broadcastChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package thaumicenergistics.mixins.ae2;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import appeng.api.networking.energy.IEnergySource;
|
||||
import appeng.api.storage.MEStorage;
|
||||
import appeng.menu.me.common.MEStorageMenu;
|
||||
|
||||
import thaumicenergistics.common.container.ThEEssentiaMenuAccess;
|
||||
|
||||
/**
|
||||
* 为 {@link MEStorageMenu} 实现 {@link ThEEssentiaMenuAccess} 接口,
|
||||
* 暴露其 protected 的 storage / energySource 供 ThaumicEnergistics 的
|
||||
* {@code EssentiaFillHelper} 访问。
|
||||
* <p>
|
||||
* 通过 mixin 向目标类追加方法(mixin 内普通方法会被注入到目标类),
|
||||
* 方法名前缀 thE_ 避免与 AE2 未来版本可能新增的方法冲突。
|
||||
*/
|
||||
@Mixin(targets = "appeng.menu.me.common.MEStorageMenu", remap = false)
|
||||
public abstract class MixinMEStorageMenuAccessors implements ThEEssentiaMenuAccess {
|
||||
|
||||
@Shadow(remap = false)
|
||||
protected MEStorage storage;
|
||||
|
||||
@Shadow(remap = false)
|
||||
protected IEnergySource energySource;
|
||||
|
||||
@Override
|
||||
public MEStorage thE_getStorage() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IEnergySource thE_getEnergySource() {
|
||||
return this.energySource;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package thaumicenergistics.mixins.ae2;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import appeng.client.gui.me.common.MEStorageScreen;
|
||||
import appeng.client.gui.me.common.RepoSlot;
|
||||
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import thaumicenergistics.common.network.EssentiaTerminalDepositC2SPacket;
|
||||
import thaumicenergistics.common.network.EssentiaTerminalFillC2SPacket;
|
||||
|
||||
/**
|
||||
* 让所有 AE2 ME 终端 GUI(ME 终端、合成终端、无线终端等)都支持源质右键交互:
|
||||
* <ul>
|
||||
* <li>Shift+右键玩家背包格:格内是源质容器 → 源质存入网络(空容器留原格)。</li>
|
||||
* <li>右键玩家物品栏/背包槽 → 放行(走 AE2 原版物品交互)。</li>
|
||||
* <li>右键网络区域(源质条目或空白):手持装有源质的罐子/安瓿 → 源质存入 ME 网络(空容器回背包)。</li>
|
||||
* <li>手持空罐/空安瓿 + 右键源质条目 → 从网络取出源质装罐/装安瓿。</li>
|
||||
* <li>左键仍是正常物品操作(不受影响)。</li>
|
||||
* </ul>
|
||||
* 源质终端({@code GuiEssentiaTerminal})已覆写 {@code mouseClicked},子类覆写优先于 mixin;
|
||||
* 此 mixin 覆盖其余未覆写的 ME 终端 GUI。
|
||||
*/
|
||||
@Mixin(targets = "appeng.client.gui.me.common.MEStorageScreen", remap = false)
|
||||
public abstract class MixinMEStorageScreen {
|
||||
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true, remap = false)
|
||||
private void thE_onMouseClickedEssentia(double xCoord, double yCoord, int btn,
|
||||
CallbackInfoReturnable<Boolean> cir) {
|
||||
if (btn != 1) {
|
||||
return;
|
||||
}
|
||||
MEStorageScreen<?> self = (MEStorageScreen<?>) (Object) this;
|
||||
Slot hovered = self.getSlotUnderMouse();
|
||||
boolean shiftDown = net.minecraft.client.gui.screens.inventory.AbstractContainerScreen.hasShiftDown();
|
||||
var carried = self.getMenu().getCarried();
|
||||
// 源质物品来源:优先菜单 carried(拾取物品);为空时回退到玩家主手物品
|
||||
// (GUI 打开时主手物品不自动进入 carried,除非先左键点击拿起)
|
||||
ItemStack sourceStack = !carried.isEmpty() ? carried
|
||||
: self.getMenu().getPlayerInventory().player.getMainHandItem();
|
||||
boolean isContainer = isSourceContainer(sourceStack);
|
||||
boolean containerHasEssentia = isContainer && hasContainerEssentia(sourceStack);
|
||||
|
||||
// Shift+右键玩家物品栏/背包格:格内是源质容器 → 存入源质(空容器留原格)
|
||||
if (shiftDown && hovered != null && self.getMenu().isPlayerSideSlot(hovered)) {
|
||||
var slotStack = hovered.getItem();
|
||||
if (slotStack.getItem() instanceof thaumcraft.api.aspects.IEssentiaContainerItem) {
|
||||
PacketDistributor.sendToServer(EssentiaTerminalDepositC2SPacket.fromSlot(
|
||||
self.getMenu().containerId, hovered.getSlotIndex(), slotStack));
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
// 非源质容器 → 放行(AE2 原版 Shift+右键快速移动)
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通右键点击玩家物品栏/背包槽 → 放行(AE2 原版物品交互)
|
||||
if (!shiftDown && hovered != null && self.getMenu().isPlayerSideSlot(hovered)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shift+右键:手持源质容器(满或空)→ 拦截。满→存入源质;空→阻止被当作普通物品放入网络
|
||||
if (shiftDown && isContainer) {
|
||||
if (containerHasEssentia) {
|
||||
// 立即本地清空 carried/主手,防止 AE2 后续把空容器当普通物品放入网络
|
||||
clearSource(self, sourceStack, carried);
|
||||
PacketDistributor.sendToServer(EssentiaTerminalDepositC2SPacket.fromClientStack(
|
||||
self.getMenu().containerId, sourceStack));
|
||||
}
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 普通右键网络区域:手持满容器 → 存入源质
|
||||
if (containerHasEssentia) {
|
||||
// 立即本地清空 carried/主手,防止 AE2 后续把空容器当普通物品放入网络
|
||||
clearSource(self, sourceStack, carried);
|
||||
PacketDistributor.sendToServer(EssentiaTerminalDepositC2SPacket.fromClientStack(
|
||||
self.getMenu().containerId, sourceStack));
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 手持空容器:右键源质条目 → 取出装罐;右键网络其他 → 拦截(防被放入网络)
|
||||
if (isContainer) {
|
||||
if (hovered instanceof RepoSlot repoSlot) {
|
||||
var entry = repoSlot.getEntry();
|
||||
if (entry != null && entry.getWhat() instanceof thaumicenergistics.common.integration.appeng.AEssentiaKey key) {
|
||||
PacketDistributor.sendToServer(EssentiaTerminalFillC2SPacket.create(
|
||||
self.getMenu().containerId, key.getId(), sourceStack));
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cir.setReturnValue(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// 非源质容器 → 放行(AE2 原版右键)
|
||||
}
|
||||
|
||||
/** 清空 source 所在位置(carried 或主手),防止被 AE2 后续当普通物品处理。 */
|
||||
private static void clearSource(MEStorageScreen<?> self, ItemStack source, ItemStack carried) {
|
||||
if (!carried.isEmpty() && carried == source) {
|
||||
self.getMenu().setCarried(net.minecraft.world.item.ItemStack.EMPTY);
|
||||
} else {
|
||||
self.getMenu().getPlayerInventory().player.getInventory().setItem(
|
||||
self.getMenu().getPlayerInventory().player.getInventory().selected,
|
||||
net.minecraft.world.item.ItemStack.EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
/** 源质容器:实现 IEssentiaContainerItem 的物品,或空玻璃安瓿(glass_phial 不实现该接口但可装罐)。 */
|
||||
private static boolean isSourceContainer(ItemStack stack) {
|
||||
if (stack.getItem() instanceof thaumcraft.api.aspects.IEssentiaContainerItem) {
|
||||
return true;
|
||||
}
|
||||
return stack.is(net.minecraft.core.registries.BuiltInRegistries.ITEM.get(
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("thaumcraft", "glass_phial")));
|
||||
}
|
||||
|
||||
/** 若手持物品是源质容器且非空,返回 true。 */
|
||||
private static boolean hasContainerEssentia(ItemStack stack) {
|
||||
if (stack.getItem() instanceof thaumcraft.api.aspects.IEssentiaContainerItem container) {
|
||||
var aspects = container.getAspects(stack);
|
||||
if (aspects != null && !aspects.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"thaumicenergistics:essentia_provider",
|
||||
"thaumicenergistics:infusion_provider",
|
||||
"thaumicenergistics:arcane_assembler",
|
||||
"thaumicenergistics:essentia_vibration_chamber",
|
||||
"thaumicenergistics:knowledge_inscriber",
|
||||
"thaumicenergistics:essentia_cell_workbench",
|
||||
"thaumicenergistics:distillation_encoder"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"thaumicenergistics:essentia_provider",
|
||||
"thaumicenergistics:infusion_provider",
|
||||
"thaumicenergistics:arcane_assembler",
|
||||
"thaumicenergistics:essentia_vibration_chamber",
|
||||
"thaumicenergistics:knowledge_inscriber",
|
||||
"thaumicenergistics:essentia_cell_workbench",
|
||||
"thaumicenergistics:distillation_encoder"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
Reference in New Issue
Block a user