样板供应器

This commit is contained in:
GaLicn 2025-09-24 20:52:35 +08:00
parent 5bd5df3468
commit 5cf2aa991f
8 changed files with 506 additions and 0 deletions

View File

@ -0,0 +1,10 @@
package com.extendedae_plus.bridge;
import appeng.api.upgrades.IUpgradeInventory;
/**
* 仅用于我方在未安装 appflux 时向逻辑类暴露自带升级槽避免与 appflux IUpgradeableObject 冲突
*/
public interface CompatUpgradeProvider {
IUpgradeInventory eap$getCompatUpgrades();
}

View File

@ -0,0 +1,12 @@
package com.extendedae_plus.bridge;
import appeng.menu.ToolboxMenu;
/**
* 提供给 AE2 菜单以暴露升级槽与工具箱面板的接口
* PatternProviderMenu mixin 会在未安装 appflux 时实现该接口
* 以便在界面上显示升级卡槽用于放置频道卡
*/
public interface IUpgradableMenu {
ToolboxMenu getToolbox();
}

View File

@ -0,0 +1,38 @@
package com.extendedae_plus.compat;
import net.neoforged.fml.loading.FMLLoader;
import net.neoforged.fml.ModList;
public final class UpgradeSlotCompat {
private static Boolean APPFLUX_LOADED;
private UpgradeSlotCompat() {}
private static boolean isAppfluxLoaded() {
if (APPFLUX_LOADED == null) {
try {
APPFLUX_LOADED = ModList.get().isLoaded("appflux");
} catch (Throwable t) {
// 早期阶段或运行环境差异
APPFLUX_LOADED = false;
}
}
return APPFLUX_LOADED;
}
// 是否由我们提供升级槽当未安装 appflux
public static boolean shouldEnableUpgradeSlots() {
return !isAppfluxLoaded();
}
// 是否启用频道卡支持两种情况下都启用
public static boolean shouldEnableChannelCard() {
return true;
}
// 客户端界面是否需要显示升级面板/不装 appflux 均显示
// appflux 提供的升级槽会以 SlotSemantics.UPGRADE 出现在菜单中我们只负责渲染面板
public static boolean shouldAddUpgradePanelToScreen() {
return true;
}
}

View File

@ -0,0 +1,67 @@
package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.api.upgrades.Upgrades;
import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.implementations.PatternProviderScreen;
import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.ToolboxPanel;
import appeng.client.gui.widgets.UpgradesPanel;
import appeng.core.localization.GuiText;
import appeng.menu.SlotSemantics;
import appeng.menu.implementations.PatternProviderMenu;
import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu;
import com.extendedae_plus.compat.UpgradeSlotCompat;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.List;
@Mixin(value = PatternProviderScreen.class, priority = 2000, remap = false)
public abstract class PatternProviderScreenUpgradesMixin<C extends PatternProviderMenu> extends AEBaseScreen<C> {
@Inject(method = "<init>", at = @At("TAIL"))
private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
if (!UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) {
return;
}
try {
this.widgets.add("upgrades", new UpgradesPanel(
menu.getSlots(SlotSemantics.UPGRADE),
this::eap$getCompatibleUpgrades));
} catch (IllegalStateException already) {
// 已存在同名面板可能由 AE2 或其他模组添加忽略
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("[样板供应器][界面] 升级面板已存在,跳过添加");
}
if (menu instanceof AEBaseMenu base && base instanceof com.extendedae_plus.bridge.IUpgradableMenu upg && upg.getToolbox() != null && upg.getToolbox().isPresent()) {
try {
this.widgets.add("toolbox", new ToolboxPanel(style, upg.getToolbox().getName()));
} catch (IllegalStateException already) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("[样板供应器][界面] 工具箱面板已存在,跳过添加");
}
}
}
@Unique
private List<Component> eap$getCompatibleUpgrades() {
var list = new ArrayList<Component>();
list.add(GuiText.CompatibleUpgrades.text());
if (menu instanceof AEBaseMenu base) {
var target = base.getTarget();
if (target instanceof PatternProviderLogicHost host) {
list.addAll(Upgrades.getTooltipLinesForMachine(host.getTerminalIcon().getItem()));
}
}
return list;
}
public PatternProviderScreenUpgradesMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
super(menu, playerInventory, title, style);
}
}

View File

@ -0,0 +1,280 @@
package com.extendedae_plus.mixin.ae2.compat;
import appeng.api.networking.IManagedGridNode;
import appeng.api.networking.security.IActionSource;
import appeng.api.upgrades.IUpgradeInventory;
import appeng.api.upgrades.IUpgradeableObject;
import appeng.api.upgrades.UpgradeInventories;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost;
import com.extendedae_plus.ae.items.ChannelCardItem;
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
import com.extendedae_plus.bridge.CompatUpgradeProvider;
import com.extendedae_plus.compat.UpgradeSlotCompat;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.wireless.WirelessSlaveLink;
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
import com.extendedae_plus.util.ExtendedAELogger;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
/**
* 样板供应器频道卡兼容实现
* - 未安装 appflux 提供 1 个升级槽并读取频道卡
* - 安装 appflux 优先从 appflux 提供的升级槽读取频道卡
* - 建立到无线主站的网格连接
*/
@Mixin(value = PatternProviderLogic.class, remap = false)
public abstract class PatternProviderLogicCompatMixin implements CompatUpgradeProvider, InterfaceWirelessLinkBridge {
@Unique
private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty();
@Unique
private WirelessSlaveLink eap$compatLink;
@Unique
private long eap$compatLastChannel = -1;
@Unique
private boolean eap$compatClientConnected = false;
@Unique
private boolean eap$compatHasInitialized = false;
@Unique
private int eap$compatDelayedInitTicks = 0;
@Final
@Shadow
private PatternProviderLogicHost host;
@Final
@Shadow
private IManagedGridNode mainNode;
@Final
@Shadow
private IActionSource actionSource;
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V",
at = @At("TAIL"))
private void eap$compatInit(IManagedGridNode mainNode, PatternProviderLogicHost host, int size, CallbackInfo ci) {
try {
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
this.eap$compatUpgrades = UpgradeInventories.forMachine(
host.getTerminalIcon().getItem(), 1, this::eap$compatOnUpgradesChanged);
ExtendedAELogger.LOGGER.debug("[样板供应器] 初始化自带升级槽 (未安装 appflux)");
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 初始化兼容升级槽失败", t);
}
}
@Unique
private void eap$compatOnUpgradesChanged() {
try {
this.host.saveChanges();
eap$compatLastChannel = -1;
eap$compatHasInitialized = false;
ExtendedAELogger.LOGGER.debug("[样板供应器] 升级变更 -> 触发初始化");
eap$compatInitializeChannelLink();
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 兼容升级变更处理失败", t);
}
}
@Inject(method = "writeToNBT", at = @At("TAIL"))
private void eap$compatWrite(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
try {
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades", registries);
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 保存兼容升级失败", t);
}
}
@Inject(method = "readFromNBT", at = @At("TAIL"))
private void eap$compatRead(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
try {
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
this.eap$compatUpgrades.readFromNBT(tag, "compat_upgrades", registries);
eap$compatLastChannel = -1;
eap$compatHasInitialized = false;
eap$compatInitializeChannelLink();
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 读取兼容升级失败", t);
}
}
@Inject(method = "addDrops", at = @At("TAIL"))
private void eap$compatDrops(List<ItemStack> drops, CallbackInfo ci) {
try {
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
for (var s : this.eap$compatUpgrades) {
if (!s.isEmpty()) drops.add(s);
}
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 掉落兼容升级失败", t);
}
}
@Inject(method = "clearContent", at = @At("TAIL"))
private void eap$compatClear(CallbackInfo ci) {
try {
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
this.eap$compatUpgrades.clear();
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 清理兼容升级失败", t);
}
}
@Inject(method = "onMainNodeStateChanged", at = @At("TAIL"))
private void eap$compatOnNodeChange(CallbackInfo ci) {
try {
eap$compatLastChannel = -1;
eap$compatHasInitialized = false;
eap$compatDelayedInitTicks = 10;
mainNode.ifPresent((grid, node) -> {
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
});
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 主节点状态变更处理失败", t);
}
}
@Override
public void eap$updateWirelessLink() {
if (eap$compatLink != null) {
eap$compatLink.updateStatus();
}
}
@Unique
public void eap$compatInitializeChannelLink() {
try {
// 客户端早退
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
return;
}
if (eap$compatHasInitialized) {
return;
}
if (mainNode == null || mainNode.getNode() == null) {
ExtendedAELogger.LOGGER.debug("[样板供应器] 初始化跳过mainNode 或 Node 不可用");
return;
}
long channel = 0L;
boolean found = false;
IUpgradeInventory upgrades = null;
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
upgrades = this.eap$compatUpgrades;
ExtendedAELogger.LOGGER.debug("[样板供应器] 使用自带升级槽(未安装 appflux: {}", upgrades != null);
} else {
// appflux 应该注入其自身的 IUpgradeableObject 实现
try {
if ((Object) this instanceof IUpgradeableObject uo) {
upgrades = uo.getUpgrades();
ExtendedAELogger.LOGGER.debug("[样板供应器] 使用 appflux 提供的升级槽: {}", upgrades != null);
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 获取第三方升级槽失败", t);
}
}
if (upgrades != null) {
for (ItemStack stack : upgrades) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack);
found = true;
ExtendedAELogger.LOGGER.debug("[样板供应器] 检测到频道卡,频道={} ", channel);
break;
}
}
}
if (!found) {
ExtendedAELogger.LOGGER.debug("[样板供应器] 未发现频道卡 -> 断开无线");
if (eap$compatLink != null) {
eap$compatLink.setFrequency(0L);
eap$compatLink.updateStatus();
}
eap$compatHasInitialized = true;
try { host.saveChanges(); } catch (Throwable ignored) {}
return;
}
if (eap$compatLink == null) {
var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode());
eap$compatLink = new WirelessSlaveLink(endpoint);
}
eap$compatLink.setFrequency(channel);
eap$compatLink.updateStatus();
ExtendedAELogger.LOGGER.debug("[样板供应器] 设置频道={} 连接状态={}", channel, eap$compatLink.isConnected());
try { host.saveChanges(); } catch (Throwable ignored) {}
mainNode.ifPresent((grid, node) -> {
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
});
if (eap$compatLink.isConnected()) {
eap$compatHasInitialized = true;
} else {
eap$compatHasInitialized = false;
eap$compatDelayedInitTicks = Math.max(eap$compatDelayedInitTicks, 5);
mainNode.ifPresent((grid, node) -> {
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
});
}
} catch (Throwable t) {
ExtendedAELogger.LOGGER.error("[样板供应器] 初始化频道链接失败", t);
}
}
@Override
public void eap$setClientWirelessState(boolean connected) {
eap$compatClientConnected = connected;
}
@Override
public boolean eap$isWirelessConnected() {
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
return eap$compatClientConnected;
} else {
return eap$compatLink != null && eap$compatLink.isConnected();
}
}
@Override
public boolean eap$hasTickInitialized() {
return eap$compatHasInitialized;
}
@Override
public void eap$setTickInitialized(boolean initialized) {
eap$compatHasInitialized = initialized;
}
// CompatUpgradeProvider 实现仅在未安装 appflux 时由我们提供升级槽
@Override
public IUpgradeInventory eap$getCompatUpgrades() {
return this.eap$compatUpgrades != null ? this.eap$compatUpgrades : UpgradeInventories.empty();
}
}

View File

@ -0,0 +1,42 @@
package com.extendedae_plus.mixin.ae2.helpers.patternprovider;
import appeng.helpers.patternprovider.PatternProviderLogic;
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* 注入到 PatternProviderLogic.Ticker 的每tick回调驱动无线链接状态更新与延迟初始化
*/
@Mixin(targets = "appeng.helpers.patternprovider.PatternProviderLogic$Ticker", remap = false)
public abstract class PatternProviderLogicTickerMixin {
// 访问内部类的外部引用字段javac 生成名 this$0
@Shadow(remap = false)
@Final
private PatternProviderLogic this$0;
@Inject(method = "tickingRequest", at = @At("HEAD"))
private void eap$tickHead(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
// 仅在服务端处理延迟初始化
if (node != null && node.getLevel() != null && node.getLevel().isClientSide) {
return;
}
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
bridge.eap$handleDelayedInit();
}
}
@Inject(method = "tickingRequest", at = @At("TAIL"))
private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
bridge.eap$updateWirelessLink();
}
}
}

View File

@ -0,0 +1,54 @@
package com.extendedae_plus.mixin.ae2.menu;
import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu;
import appeng.menu.ToolboxMenu;
import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.bridge.IUpgradableMenu;
import com.extendedae_plus.compat.UpgradeSlotCompat;
import com.extendedae_plus.bridge.CompatUpgradeProvider;
import appeng.api.upgrades.IUpgradeableObject;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.extendedae_plus.util.ExtendedAELogger;
@Mixin(value = PatternProviderMenu.class, priority = 2000, remap = false)
public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implements IUpgradableMenu {
@Final
@Shadow protected PatternProviderLogic logic;
@Unique
private ToolboxMenu eap$toolbox;
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V",
at = @At("TAIL"))
private void eap$initUpgrades(MenuType<?> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
this.eap$toolbox = new ToolboxMenu(this);
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
// 未安装 appflux使用我们提供的升级槽
ExtendedAELogger.LOGGER.debug("[样板供应器][菜单] 注入升级槽: 使用自带 compat 槽");
this.setupUpgrades(((CompatUpgradeProvider) this.logic).eap$getCompatUpgrades());
} else {
// 安装 appflux使用 appflux 注入到 PatternProviderLogic 的升级槽
ExtendedAELogger.LOGGER.debug("[样板供应器][菜单] 注入升级槽: 使用 appflux 槽");
this.setupUpgrades(((IUpgradeableObject) this.logic).getUpgrades());
}
}
@Override
public ToolboxMenu getToolbox() {
return this.eap$toolbox;
}
public PatternProviderMenuUpgradesMixin(MenuType<?> menuType, int id, Inventory playerInventory, Object host) {
super(menuType, id, playerInventory, host);
}
}

View File

@ -25,10 +25,13 @@
"ae2.autopattern.CraftingTreeNodeMixin", "ae2.autopattern.CraftingTreeNodeMixin",
"ae2.autopattern.CraftingTreeProcessMixin", "ae2.autopattern.CraftingTreeProcessMixin",
"ae2.autopattern.PatternProviderLogicContainsRedirectMixin", "ae2.autopattern.PatternProviderLogicContainsRedirectMixin",
"ae2.compat.PatternProviderLogicCompatMixin",
"ae2.helpers.InterfaceLogicChannelCardMixin", "ae2.helpers.InterfaceLogicChannelCardMixin",
"ae2.helpers.InterfaceLogicTickerMixin", "ae2.helpers.InterfaceLogicTickerMixin",
"ae2.helpers.PatternProviderLogicAdvancedMixin", "ae2.helpers.PatternProviderLogicAdvancedMixin",
"ae2.helpers.PatternProviderLogicDoublingMixin", "ae2.helpers.PatternProviderLogicDoublingMixin",
"ae2.helpers.patternprovider.PatternProviderLogicTickerMixin",
"ae2.menu.PatternProviderMenuUpgradesMixin",
"ae2.parts.automation.IOBusPartChannelCardMixin", "ae2.parts.automation.IOBusPartChannelCardMixin",
"ae2.parts.storagebus.StorageBusPartChannelCardMixin", "ae2.parts.storagebus.StorageBusPartChannelCardMixin",
"ae2.menu.ContainerPatternEncodingTermMenuMixin", "ae2.menu.ContainerPatternEncodingTermMenuMixin",