Merge branch 'refs/heads/develop/fix' into develop/gtceu_support
# Conflicts: # gradle.properties # src/main/resources/extendedae_plus.mixins.json
This commit is contained in:
commit
f991a07653
|
|
@ -42,6 +42,6 @@ ExtendedAE Plus 是一个面向 Applied Energistics 2 与 ExtendedAE 的功能
|
|||
- Applied Energistics 2(AE2):MIT License
|
||||
- SpongePowered Mixin:MIT License
|
||||
- Configuration(by Toma):MIT License
|
||||
- AE2Things(by ProjectET):MIT License
|
||||
- AE2Things-Forge(by Technici4n):MIT License
|
||||
|
||||
请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ dependencies {
|
|||
|
||||
annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor"
|
||||
|
||||
modCompileOnly "curse.maven:applied-flux-965012:${applied_flux_version}"
|
||||
modImplementation "curse.maven:applied-flux-965012:6755986"
|
||||
modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}"
|
||||
modCompileOnly "curse.maven:jade-324717:${jade_version}"
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
|
|||
loom.platform = forge
|
||||
|
||||
# Mod properties
|
||||
mod_version = 1.4.1-beta-gtm
|
||||
mod_version = 1.4.3-gtm
|
||||
maven_group = com.extendedae_plus
|
||||
archives_name = extendedae_plus
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@ package com.extendedae_plus;
|
|||
import appeng.api.storage.StorageCells;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||
import com.extendedae_plus.client.ClientRegistrar;
|
||||
import com.extendedae_plus.command.InfinityDiskGiveCommand;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.*;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ModelEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.level.LevelEvent;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
|
@ -52,13 +52,11 @@ public class ExtendedAEPlus {
|
|||
|
||||
// 注册到Forge事件总线
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
|
||||
// 注册命令注册监听
|
||||
MinecraftForge.EVENT_BUS.addListener(this::onRegisterCommands);
|
||||
// 注册通用配置
|
||||
ModConfig.init();
|
||||
// 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush)
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
|
||||
// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
|
||||
MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::worldTick);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -74,6 +72,14 @@ public class ExtendedAEPlus {
|
|||
ModNetwork.register();
|
||||
// 注册自定义 Curios 宿主定位器,便于将菜单宿主信息在服务端与客户端间同步
|
||||
MenuLocators.register(CuriosItemLocator.class, CuriosItemLocator::writeToPacket, CuriosItemLocator::readFromPacket);
|
||||
|
||||
// 绑定方块实体类型,避免 blockEntityClass 为 null 的问题
|
||||
ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get().setBlockEntity(
|
||||
com.extendedae_plus.content.matrix.UploadCoreBlockEntity.class,
|
||||
ModBlockEntities.UPLOAD_CORE_BE.get(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +123,16 @@ public class ExtendedAEPlus {
|
|||
}
|
||||
}
|
||||
|
||||
// 在世界加载时注册/加载 SavedData
|
||||
private static void onLevelLoad(LevelEvent.Load event) {
|
||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
||||
InfinityStorageManager.getForLevel(serverLevel);
|
||||
|
||||
public static InfinityStorageManager STORAGE_INSTANCE = new InfinityStorageManager();
|
||||
|
||||
public static void worldTick(TickEvent.LevelTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.START && event.side.isServer()) {
|
||||
STORAGE_INSTANCE = InfinityStorageManager.getInstance(event.level.getServer());
|
||||
}
|
||||
}
|
||||
|
||||
private void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
InfinityDiskGiveCommand.register(event.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,30 +5,15 @@ import appeng.api.storage.cells.ISaveProvider;
|
|||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* InfinityBigIntegerCellHandler
|
||||
*
|
||||
* 该类实现 AE2 的 ICellHandler,用于:
|
||||
* - 判定某个 ItemStack 是否为本 mod 的 Infinity 存储单元
|
||||
* - 在 AE2 请求访问或创建存储单元时,创建并返回对应的 StorageCell 实例
|
||||
*/
|
||||
public class InfinityBigIntegerCellHandler implements ICellHandler {
|
||||
|
||||
/** Handler 单例,供注册与调用使用 */
|
||||
public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler();
|
||||
|
||||
/**
|
||||
* 判断给定的 ItemStack 是否为 InfinityBigIntegerCell
|
||||
*/
|
||||
@Override
|
||||
public boolean isCell(ItemStack is) {
|
||||
return is.getItem() instanceof InfinityBigIntegerCellItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AE2 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventory(StorageCell 实现)。
|
||||
* 参数 container 为 AE2 提供的保存回调(ISaveProvider),当 cell 需要持久化时会调用它。
|
||||
*/
|
||||
@Override
|
||||
public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
|
||||
return InfinityBigIntegerCellInventory.createInventory(is, container);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import appeng.api.stacks.KeyCounter;
|
|||
import appeng.api.storage.cells.CellState;
|
||||
import appeng.api.storage.cells.ISaveProvider;
|
||||
import appeng.api.storage.cells.StorageCell;
|
||||
import appeng.core.AELog;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import com.extendedae_plus.util.storage.InfinityConstants;
|
||||
import com.extendedae_plus.util.storage.InfinityDataStorage;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
|
|
@ -17,117 +20,50 @@ import net.minecraft.nbt.CompoundTag;
|
|||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
/**
|
||||
* InfinityBigIntegerCellInventory
|
||||
* <p>
|
||||
* 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。
|
||||
* 主要职责:
|
||||
* - 在内存中维护条目映射 (AEKey -> BigInteger 数量)
|
||||
* - 提供插入/提取/列举/持久化等操作的实现
|
||||
* - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化
|
||||
* <p>
|
||||
* 重要字段:
|
||||
* - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息
|
||||
* - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化
|
||||
* - storedMap: 延迟初始化的内存映射,减少未使用时内存占用
|
||||
* - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描
|
||||
* - isPersisted: 标记内存状态是否已同步到持久层
|
||||
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||
* Original copyright (c) Technici4n<p>
|
||||
*/
|
||||
public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||
|
||||
// 待持久化队列(用于 debounce:在服务器 tick 中合并持久化)
|
||||
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
|
||||
// 数字格式化对象,保留两位小数(复用以减少对象分配)
|
||||
private static final DecimalFormat DF = new DecimalFormat("#.##");
|
||||
|
||||
// 关联的 ItemStack(含可能的 uuid NBT)
|
||||
private final ItemStack stack;
|
||||
private final InfinityBigIntegerCellItem cell;
|
||||
// 磁盘本身
|
||||
private final ItemStack self;
|
||||
// AE2 提供的保存提供者,用于在容器中批量保存时触发回调
|
||||
private final ISaveProvider container;
|
||||
// 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化)
|
||||
private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
|
||||
// 存储物品键和数量的映射
|
||||
private Object2ObjectMap<AEKey, BigInteger> AEKey2AmountsMap;
|
||||
// 存储的物品种类数量
|
||||
private int totalAEKeyType;
|
||||
// 存储的物品总数
|
||||
private BigInteger totalAEKey2Amounts = BigInteger.ZERO;
|
||||
// 标记是否已持久化到 SavedData
|
||||
private boolean isPersisted = true;
|
||||
// 缓存的总存储量,避免每次调用进行全表扫描
|
||||
private BigInteger totalStored = BigInteger.ZERO;
|
||||
|
||||
/**
|
||||
* 私有构造器:通过 createInventory 工厂方法调用
|
||||
*
|
||||
* @param stack 关联的物品堆
|
||||
* @param saveProvider AE2 的保存回调(可为 null)
|
||||
*/
|
||||
private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) {
|
||||
this.stack = stack;
|
||||
container = saveProvider;
|
||||
// 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化
|
||||
this.storedMap = null;
|
||||
}
|
||||
|
||||
// 创建存储单元库存实例的静态方法
|
||||
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
|
||||
if (stack.getItem() instanceof InfinityBigIntegerCellItem) {
|
||||
return new InfinityBigIntegerCellInventory(stack, saveProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取全局存储实例
|
||||
private static InfinityStorageManager getStorageInstance() {
|
||||
return InfinityStorageManager.INSTANCE;
|
||||
}
|
||||
|
||||
// 服务器 tick 回调:合并并执行待持久化项
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
// 处理本次 tick 中的全部待持久化项
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在服务器停止时被调用,立即强制持久化队列中的所有实例
|
||||
public static void onServerStopping(ServerStoppingEvent event) {
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发)
|
||||
try {
|
||||
var stor = getStorageInstance();
|
||||
if (stor != null) stor.setDirty();
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage());
|
||||
}
|
||||
public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell, ItemStack stack, ISaveProvider saveProvider) {
|
||||
// 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性
|
||||
this.cell = cell;
|
||||
// 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据
|
||||
this.self = stack;
|
||||
// 保存提供者,用于触发数据保存
|
||||
this.container = saveProvider;
|
||||
// 初始化 storedAmounts 为 null,延迟加载物品数据
|
||||
this.AEKey2AmountsMap = null;
|
||||
// 初始化磁盘数据
|
||||
initData();
|
||||
}
|
||||
|
||||
// 将 BigInteger 格式化为带单位的字符串,保留两位小数
|
||||
public static String formatBigInteger(BigInteger number) {
|
||||
// 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行
|
||||
// 使用方法局部的 DecimalFormat,避免静态共享的非线程安全问题
|
||||
java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");
|
||||
BigDecimal bd = new BigDecimal(number);
|
||||
BigDecimal thousand = new BigDecimal(1000);
|
||||
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
|
||||
|
|
@ -139,26 +75,46 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
|||
if (idx == 0) {
|
||||
return bd.setScale(0, RoundingMode.DOWN).toPlainString();
|
||||
}
|
||||
return DF.format(bd.doubleValue()) + units[idx];
|
||||
return df.format(bd.doubleValue()) + units[idx];
|
||||
}
|
||||
|
||||
// 获取当前存储单元的数据存储对象
|
||||
// 获取磁盘的 InfinityDataStorage 数据
|
||||
private InfinityDataStorage getCellStorage() {
|
||||
if (this.getUUID() == null) {
|
||||
// 如果没有UUID,返回空存储
|
||||
return InfinityDataStorage.EMPTY;
|
||||
// 如果磁盘有 UUID,返回对应的 InfinityDataStorage
|
||||
if (getUUID() != null) {
|
||||
return getStorageManagerInstance().getOrCreateCell(getUUID());
|
||||
} else {
|
||||
// 否则获取或创建对应UUID的存储
|
||||
return getStorageInstance().getOrCreateCell(getUUID());
|
||||
// 否则返回空的 InfinityDataStorage
|
||||
return InfinityDataStorage.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取存储单元状态(空/非空)
|
||||
// 初始化磁盘数据
|
||||
private void initData() {
|
||||
// 如果磁盘有 UUID,加载存储的物品数据
|
||||
if (hasUUID()) {
|
||||
this.totalAEKeyType = getCellStorage().amounts.size();
|
||||
this.totalAEKey2Amounts = getCellStorage().itemCount.equals(BigInteger.ZERO) ?
|
||||
BigInteger.ZERO :
|
||||
getCellStorage().itemCount;
|
||||
|
||||
} else {
|
||||
// 否则初始化为空
|
||||
this.totalAEKeyType = 0;
|
||||
this.totalAEKey2Amounts = BigInteger.ZERO;
|
||||
// 加载物品数据
|
||||
getCellStoredMap();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取存储单元的状态(空、部分填充)
|
||||
@Override
|
||||
public CellState getStatus() {
|
||||
if (this.getCellStoredMap().isEmpty()) {
|
||||
// 如果没有存储任何物品,返回空状态
|
||||
if (this.getTotalAEKey2Amounts().equals(BigInteger.ZERO)) {
|
||||
return CellState.EMPTY;
|
||||
}
|
||||
// 否则返回满状态
|
||||
return CellState.NOT_EMPTY;
|
||||
}
|
||||
|
||||
|
|
@ -168,201 +124,226 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
|||
return 512;
|
||||
}
|
||||
|
||||
// 持久化存储单元数据到全局存储
|
||||
@Override
|
||||
public void persist() {
|
||||
if (this.isPersisted)
|
||||
return;
|
||||
|
||||
if (totalAEKey2Amounts.equals(BigInteger.ZERO)) {
|
||||
if (hasUUID()) {
|
||||
getStorageManagerInstance().removeCell(getUUID());
|
||||
if (self.hasTag()) {
|
||||
var tag = self.getTag();
|
||||
// remove persisted identifiers and cached summary fields from the ItemStack
|
||||
tag.remove(InfinityConstants.INFINITY_CELL_UUID);
|
||||
tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL);
|
||||
tag.remove(InfinityConstants.INFINITY_ITEM_TYPES);
|
||||
// backward compat: also remove internal cell item count key if present
|
||||
tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
|
||||
}
|
||||
initData();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建物品键列表
|
||||
ListTag keys = new ListTag();
|
||||
// 创建物品数量列表
|
||||
ListTag amounts = new ListTag();
|
||||
// 初始化物品总数
|
||||
BigInteger itemCount = BigInteger.ZERO;
|
||||
|
||||
for (var entry : this.AEKey2AmountsMap.object2ObjectEntrySet()) {
|
||||
BigInteger amount = entry.getValue();
|
||||
// 如果数量大于 0,添加到键和数量列表
|
||||
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
||||
keys.add(entry.getKey().toTagGeneric());
|
||||
CompoundTag amountTag = new CompoundTag();
|
||||
amountTag.putByteArray("value", amount.toByteArray());
|
||||
amounts.add(amountTag);
|
||||
|
||||
itemCount = itemCount.add(amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (keys.isEmpty()) {
|
||||
getStorageManagerInstance().updateCell(getUUID(), new InfinityDataStorage());
|
||||
} else {
|
||||
getStorageManagerInstance().modifyDisk(getUUID(), keys, amounts, itemCount);
|
||||
}
|
||||
|
||||
// 更新存储的物品种类数量
|
||||
this.totalAEKeyType = this.AEKey2AmountsMap.size();
|
||||
// 更新存储的物品总数
|
||||
this.totalAEKey2Amounts = itemCount;
|
||||
// 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本
|
||||
var tag = self.getOrCreateTag();
|
||||
tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray());
|
||||
tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType);
|
||||
// backward compat storage field (kept for legacy readers)
|
||||
tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray());
|
||||
|
||||
// 标记数据已持久化
|
||||
this.isPersisted = true;
|
||||
}
|
||||
|
||||
// 获取存储单元的描述(此处返回null,可自定义)
|
||||
@Override
|
||||
public Component getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 静态方法,创建存储单元库存
|
||||
public static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
|
||||
// 检查物品堆栈是否为空
|
||||
Objects.requireNonNull(stack, "Cannot create cell inventory for null itemstack");
|
||||
// 检查物品是否为 IDISKCellItem 类型
|
||||
if (!(stack.getItem() instanceof InfinityBigIntegerCellItem cell)) {
|
||||
return null;
|
||||
}
|
||||
// 创建并返回新的 DISKCellInventory 实例
|
||||
return new InfinityBigIntegerCellInventory(cell, stack, saveProvider);
|
||||
}
|
||||
|
||||
// 获取存储的物品总数
|
||||
public BigInteger getTotalAEKey2Amounts() {
|
||||
return this.totalAEKey2Amounts;
|
||||
}
|
||||
|
||||
// 获取存储的物品种类数量
|
||||
public int getTotalAEKeyType() {
|
||||
return this.totalAEKeyType;
|
||||
}
|
||||
|
||||
// 判断物品堆栈是否有UUID
|
||||
public boolean hasUUID() {
|
||||
return stack.hasTag() && stack.getOrCreateTag().contains("uuid");
|
||||
return self.hasTag() && self.getOrCreateTag().contains(InfinityConstants.INFINITY_CELL_UUID);
|
||||
}
|
||||
|
||||
// 获取物品堆栈的UUID
|
||||
public UUID getUUID() {
|
||||
if (this.hasUUID())
|
||||
return stack.getOrCreateTag().getUUID("uuid");
|
||||
else
|
||||
if (this.hasUUID()) {
|
||||
return self.getOrCreateTag().getUUID(InfinityConstants.INFINITY_CELL_UUID);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取或初始化存储映射
|
||||
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
|
||||
if (storedMap == null) {
|
||||
storedMap = new Object2ObjectOpenHashMap<>();
|
||||
if (AEKey2AmountsMap == null) {
|
||||
AEKey2AmountsMap = new Object2ObjectOpenHashMap<>();
|
||||
this.loadCellStoredMap();
|
||||
}
|
||||
return storedMap;
|
||||
}
|
||||
|
||||
// 从存储中加载物品映射
|
||||
private void loadCellStoredMap() {
|
||||
boolean corruptedTag = false; // 标记数据是否损坏
|
||||
if (!stack.hasTag()) return;
|
||||
ListTag keys = this.getCellStorage().keys;
|
||||
ListTag amounts = this.getCellStorage().amounts;
|
||||
int len = Math.min(keys.size(), amounts.size());
|
||||
for (int i = 0; i < len; i++) {
|
||||
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
|
||||
CompoundTag amtTag = amounts.getCompound(i);
|
||||
try {
|
||||
BigInteger amount;
|
||||
if (amtTag.contains("l")) {
|
||||
long v = amtTag.getLong("l");
|
||||
amount = BigInteger.valueOf(v);
|
||||
} else if (amtTag.contains("s")) {
|
||||
amount = new BigInteger(amtTag.getString("s"));
|
||||
} else {
|
||||
corruptedTag = true;
|
||||
continue;
|
||||
}
|
||||
if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
|
||||
corruptedTag = true;
|
||||
} else {
|
||||
// storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销
|
||||
storedMap.put(key, amount);
|
||||
// 更新缓存的总数
|
||||
totalStored = totalStored.add(amount);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
corruptedTag = true;
|
||||
}
|
||||
}
|
||||
// 如果有损坏,保存修正后的数据
|
||||
if (corruptedTag) {
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// 标记数据需要保存,并通知容器或直接持久化
|
||||
private void saveChanges() {
|
||||
// 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O
|
||||
isPersisted = false;
|
||||
if (container != null) {
|
||||
// 当存在容器时,优先让容器统一处理持久化
|
||||
container.saveChanges();
|
||||
} else {
|
||||
// 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O
|
||||
if (!PENDING_PERSIST.contains(this)) {
|
||||
PENDING_PERSIST.offer(this);
|
||||
}
|
||||
}
|
||||
return AEKey2AmountsMap;
|
||||
}
|
||||
|
||||
// 获取所有可用的物品堆栈及其数量
|
||||
@Override
|
||||
public void getAvailableStacks(KeyCounter out) {
|
||||
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
Object2ObjectMap<AEKey, BigInteger> map = getCellStoredMap();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
if(this.getCellStoredMap() == null) return;
|
||||
for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) {
|
||||
AEKey key = entry.getKey();
|
||||
BigInteger value = entry.getValue();
|
||||
|
||||
// 当前 KeyCounter 中已有的值(long)
|
||||
// 获取 KeyCounter 中已有的值
|
||||
long existing = out.get(key);
|
||||
|
||||
// 将 existing 与当前 value 做 BigInteger 累加并饱和到 Long.MAX_VALUE
|
||||
// 计算总和并限制到 Long.MAX_VALUE
|
||||
BigInteger sum = BigInteger.valueOf(existing).add(value);
|
||||
long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue();
|
||||
|
||||
// KeyCounter 没有 set(key,long) 的统一接口暴露(只有 add/remove),所以先移除已存在的值再设置。
|
||||
// 为避免读取-写入竞争,我们计算出要新增的 delta 并调用 add(key, delta)
|
||||
// 更新 KeyCounter
|
||||
if (existing == Long.MAX_VALUE) {
|
||||
// 已经饱和,无需再添加
|
||||
continue;
|
||||
}
|
||||
long delta;
|
||||
if (toSet == Long.MAX_VALUE) {
|
||||
delta = Long.MAX_VALUE - existing;
|
||||
} else {
|
||||
delta = toSet - existing;
|
||||
}
|
||||
long delta = toSet - existing;
|
||||
if (delta != 0) {
|
||||
out.add(key, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 持久化存储单元数据到全局存储
|
||||
@Override
|
||||
public void persist() {
|
||||
if (this.isPersisted)
|
||||
return;
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
if (map.isEmpty()) {
|
||||
// 如果存储为空,移除UUID和全局存储中的数据
|
||||
if (this.hasUUID()) {
|
||||
getStorageInstance().removeCell(getUUID());
|
||||
if (stack.getTag() != null) {
|
||||
stack.getTag().remove("uuid");
|
||||
// 移除缓存的 total 字段
|
||||
stack.getTag().remove("total");
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
// 从存储中加载物品映射
|
||||
private void loadCellStoredMap() {
|
||||
boolean dataCorruption = false;
|
||||
if (!self.hasTag()) return;
|
||||
|
||||
var keys = getCellStorage().keys;
|
||||
var amounts = getCellStorage().amounts;
|
||||
// 数据损坏
|
||||
if (keys.size() != amounts.size()) {
|
||||
AELog.warn("Loading storage cell with mismatched amounts/tags: %d != %d", amounts.size(), keys.size());
|
||||
}
|
||||
// 构建要保存的Key和数量列表(混合表示:long 或 string)
|
||||
ListTag amountTags = new ListTag();
|
||||
ListTag keys = new ListTag();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
BigInteger amount = entry.getValue();
|
||||
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
||||
keys.add(entry.getKey().toTagGeneric());
|
||||
CompoundTag amt = new CompoundTag();
|
||||
if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
amt.putLong("l", amount.longValue());
|
||||
} else {
|
||||
amt.putString("s", amount.toString());
|
||||
}
|
||||
amountTags.add(amt);
|
||||
}
|
||||
}
|
||||
// 如果没有Key,更新为空存储,否则保存数据
|
||||
if (keys.isEmpty()) {
|
||||
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
|
||||
} else {
|
||||
// amounts 现在为 CompoundTag 列表
|
||||
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags);
|
||||
}
|
||||
// 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long
|
||||
if (stack.getOrCreateTag() != null) {
|
||||
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
stack.getOrCreateTag().putLong("total", totalStored.longValue());
|
||||
// 遍历数量和键,加载到 AEKey2AmountsMap
|
||||
for (int i = 0; i < amounts.size(); i++) {
|
||||
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
|
||||
BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value"));
|
||||
// 检查数据是否损坏
|
||||
if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
|
||||
dataCorruption = true;
|
||||
} else {
|
||||
stack.getOrCreateTag().putString("total", totalStored.toString());
|
||||
AEKey2AmountsMap.put(key, amount);
|
||||
}
|
||||
// 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示
|
||||
int typesCount = this.getCellStoredMap().size();
|
||||
stack.getOrCreateTag().putInt("types", typesCount);
|
||||
}
|
||||
isPersisted = true;
|
||||
if (dataCorruption) {
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取全局存储实例
|
||||
private static InfinityStorageManager getStorageManagerInstance() {
|
||||
return ExtendedAEPlus.STORAGE_INSTANCE;
|
||||
}
|
||||
|
||||
// 标记数据需要保存,并通知容器或直接持久化
|
||||
private void saveChanges() {
|
||||
// 更新存储的物品种类数量
|
||||
this.totalAEKeyType = this.AEKey2AmountsMap.size();
|
||||
// 重置物品总数
|
||||
this.totalAEKey2Amounts = BigInteger.ZERO;
|
||||
// 计算物品总数
|
||||
for (BigInteger AEKey2Amounts : this.AEKey2AmountsMap.values()) {
|
||||
this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(AEKey2Amounts);
|
||||
}
|
||||
// 标记数据未持久化
|
||||
this.isPersisted = false;
|
||||
// 如果有保存提供者,通知保存
|
||||
if (this.container != null) {
|
||||
this.container.saveChanges();
|
||||
} else {
|
||||
// 否则立即持久化
|
||||
this.persist();
|
||||
}
|
||||
}
|
||||
|
||||
// 插入物品到存储单元
|
||||
@Override
|
||||
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||
// 数量为0或类型不匹配直接返回
|
||||
if (amount == 0)
|
||||
if (amount == 0){
|
||||
return 0;
|
||||
// 不允许存储无限单元自身
|
||||
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
|
||||
return 0;
|
||||
// 如果没有UUID,生成UUID并初始化存储
|
||||
if (!this.hasUUID()) {
|
||||
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
|
||||
getStorageInstance().getOrCreateCell(getUUID());
|
||||
// 确保 storedMap 初始化并从持久层加载数据
|
||||
this.getCellStoredMap();
|
||||
}
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
||||
// 不允许存储无限单元自身
|
||||
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果没有UUID,尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储
|
||||
if (!this.hasUUID()) {
|
||||
self.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, UUID.randomUUID());
|
||||
getStorageManagerInstance().getOrCreateCell(getUUID());
|
||||
loadCellStoredMap();
|
||||
}
|
||||
// 获取当前物品数量
|
||||
BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
|
||||
|
||||
if (mode == Actionable.MODULATE) {
|
||||
// 实际插入,更新数量并保存
|
||||
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
|
||||
map.put(what, newAmount);
|
||||
// 更新 cached total
|
||||
totalStored = totalStored.add(BigInteger.valueOf(amount));
|
||||
getCellStoredMap().put(what, newAmount);
|
||||
this.saveChanges();
|
||||
}
|
||||
return amount;
|
||||
|
|
@ -371,35 +352,26 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
|||
// 从存储单元提取物品
|
||||
@Override
|
||||
public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
||||
BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
|
||||
// 如果有物品可提取
|
||||
if (currentAmount.compareTo(BigInteger.ZERO) > 0) {
|
||||
|
||||
BigInteger requested = BigInteger.valueOf(amount);
|
||||
if (currentAmount.compareTo(requested) <= 0) {
|
||||
// 提取全部
|
||||
long ret;
|
||||
if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
|
||||
ret = Long.MAX_VALUE;
|
||||
} else {
|
||||
ret = currentAmount.longValue();
|
||||
}
|
||||
|
||||
// 如果提取数量大于等于当前数量
|
||||
if (requested.compareTo(currentAmount) >= 0) {
|
||||
if (mode == Actionable.MODULATE) {
|
||||
map.remove(what);
|
||||
// 更新 cached total
|
||||
// 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数
|
||||
totalStored = totalStored.subtract(currentAmount);
|
||||
getCellStoredMap().remove(what);
|
||||
this.saveChanges();
|
||||
}
|
||||
return ret;
|
||||
return currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : currentAmount.longValue();
|
||||
} else {
|
||||
// 提取部分
|
||||
// 提取部分数量
|
||||
if (mode == Actionable.MODULATE) {
|
||||
map.put(what, currentAmount.subtract(requested));
|
||||
// 更新 cached total
|
||||
totalStored = totalStored.subtract(requested);
|
||||
getCellStoredMap().put(what, currentAmount.subtract(requested));
|
||||
this.saveChanges();
|
||||
}
|
||||
return amount;
|
||||
return requested.longValue();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -408,6 +380,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
|||
// 获取存储单元内所有物品的总数量(格式化字符串)
|
||||
public String getTotalStorage() {
|
||||
// 使用缓存的 totalStored,避免每次全表扫描
|
||||
return formatBigInteger(totalStored);
|
||||
return formatBigInteger(totalAEKey2Amounts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package com.extendedae_plus.ae.items;
|
||||
|
||||
import appeng.items.materials.UpgradeCardItem;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 频道卡(MVP):仅存储一个 long 类型的频道号到 NBT:"channel"。
|
||||
* 继承 AE2 的 UpgradeCardItem 以复用升级卡判定与提示框架。
|
||||
*/
|
||||
public class ChannelCardItem extends UpgradeCardItem {
|
||||
public static final String TAG_CHANNEL = "channel";
|
||||
|
||||
public ChannelCardItem(Item.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static void setChannel(ItemStack stack, long channel) {
|
||||
CompoundTag tag = stack.getOrCreateTag();
|
||||
tag.putLong(TAG_CHANNEL, channel);
|
||||
}
|
||||
|
||||
public static long getChannel(ItemStack stack) {
|
||||
CompoundTag tag = stack.getTag();
|
||||
return tag != null && tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> lines, TooltipFlag flag) {
|
||||
super.appendHoverText(stack, level, lines, flag);
|
||||
long ch = getChannel(stack);
|
||||
if (ch == 0L) {
|
||||
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset"));
|
||||
} else {
|
||||
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||
ItemStack stack = player.getItemInHand(hand);
|
||||
if (!level.isClientSide) {
|
||||
long ch = getChannel(stack);
|
||||
boolean dec = player.isShiftKeyDown();
|
||||
long next = dec ? Math.max(0L, ch - 1L) : ch + 1L;
|
||||
if (next != ch) {
|
||||
setChannel(stack, next);
|
||||
player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true);
|
||||
}
|
||||
}
|
||||
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,31 @@
|
|||
package com.extendedae_plus.ae.items;
|
||||
|
||||
import appeng.api.config.FuzzyMode;
|
||||
import appeng.api.storage.cells.ICellWorkbenchItem;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||
import com.extendedae_plus.util.storage.InfinityConstants;
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.LongTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class InfinityBigIntegerCellItem extends Item {
|
||||
public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem {
|
||||
|
||||
public InfinityBigIntegerCellItem() {
|
||||
super(new Properties().stacksTo(1).fireResistant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在物品悬停提示中展示额外信息。
|
||||
* 功能:
|
||||
* - 若 ItemStack 的 NBT 含有 UUID,则显示该 UUID(不会触发服务器加载或持久化行为)
|
||||
* - 若 NBT 同步了 total 字段,则读取并格式化显示总存储量(使用 Inventory 的 formatBigInteger)
|
||||
*
|
||||
* 设计说明:客户端 tooltip 不主动访问服务端 SavedData,以避免不必要的 I/O 与状态变更。
|
||||
*/
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack,
|
||||
@Nullable Level world,
|
||||
|
|
@ -42,15 +37,16 @@ public class InfinityBigIntegerCellItem extends Item {
|
|||
Preconditions.checkArgument(stack.getItem() == this);
|
||||
// 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
|
||||
CompoundTag tag = stack.getTag();
|
||||
if (tag != null && tag.contains("uuid")) {
|
||||
String uuidStr = tag.getUUID("uuid").toString();
|
||||
if (tag != null && tag.contains(InfinityConstants.INFINITY_CELL_UUID)) {
|
||||
String uuidStr = tag.getUUID(InfinityConstants.INFINITY_CELL_UUID).toString();
|
||||
tooltip.add(
|
||||
Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
|
||||
);
|
||||
// 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey
|
||||
if (tag.contains("types")) {
|
||||
|
||||
// 显示已缓存的种类数量(types)——优先使用 ItemStack 缓存字段
|
||||
if (tag.contains(InfinityConstants.INFINITY_ITEM_TYPES)) {
|
||||
try {
|
||||
int types = tag.getInt("types");
|
||||
int types = tag.getInt(InfinityConstants.INFINITY_ITEM_TYPES);
|
||||
tooltip.add(
|
||||
Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
|
||||
);
|
||||
|
|
@ -58,25 +54,51 @@ public class InfinityBigIntegerCellItem extends Item {
|
|||
// ignore malformed value
|
||||
}
|
||||
}
|
||||
// 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位
|
||||
if (tag.contains("total")) {
|
||||
BigInteger total = BigInteger.ZERO;
|
||||
Tag t = tag.get("total");
|
||||
|
||||
// 显示物品总数(formatted)。优先使用缓存的 INFINITY_ITEM_TOTAL 字段(byte[]),否则回退为 legacy 字段或不显示
|
||||
if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) {
|
||||
try {
|
||||
if (t instanceof LongTag) {
|
||||
total = BigInteger.valueOf(tag.getLong("total"));
|
||||
} else {
|
||||
String s = tag.getString("total");
|
||||
total = new BigInteger(s);
|
||||
}
|
||||
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL);
|
||||
java.math.BigInteger total = new java.math.BigInteger(bytes);
|
||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||
tooltip.add(
|
||||
Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||
);
|
||||
} catch (Exception ignored) {
|
||||
// 解析失败保持为 0
|
||||
// ignore malformed value
|
||||
}
|
||||
} else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) {
|
||||
try {
|
||||
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
|
||||
java.math.BigInteger total = new java.math.BigInteger(bytes);
|
||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||
tooltip.add(
|
||||
Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||
);
|
||||
} catch (Exception ignored) {
|
||||
// ignore malformed value
|
||||
}
|
||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||
tooltip.add(
|
||||
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个带有指定 UUID 的 Infinity 磁盘 ItemStack
|
||||
*/
|
||||
public static ItemStack withUUID(java.util.UUID uuid) {
|
||||
ItemStack stack = new ItemStack(Objects.requireNonNull(
|
||||
ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell")
|
||||
)));
|
||||
stack.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, uuid);
|
||||
return stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FuzzyMode getFuzzyMode(ItemStack itemStack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFuzzyMode(ItemStack itemStack, FuzzyMode fuzzyMode) {
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,8 @@ public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart
|
|||
@GuiSync(719) public int effectiveSpeed = 1;
|
||||
@GuiSync(720) public double multiplier = 1.0;
|
||||
@GuiSync(721) public boolean targetBlacklisted = false;
|
||||
// 来自部件的网络能量不足提示(服务端设置,客户端用于显示警告)
|
||||
@GuiSync(722) public boolean networkEnergyInsufficient = false;
|
||||
|
||||
public boolean getAccelerateEnabled() {
|
||||
return this.accelerateEnabled;
|
||||
|
|
@ -97,6 +99,14 @@ public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart
|
|||
this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
|
||||
}
|
||||
|
||||
// 从部件同步网络能量不足状态(防御性编程,避免 NPE)
|
||||
try {
|
||||
EntitySpeedTickerPart host = getHost();
|
||||
if (host != null) {
|
||||
this.networkEnergyInsufficient = host.isNetworkEnergyInsufficient();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 如果在客户端,刷新界面
|
||||
if (isClientSide()) {
|
||||
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
|
||||
|
|
|
|||
|
|
@ -84,16 +84,36 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
|
|||
|
||||
// 控制是否启用加速(默认启用)
|
||||
private boolean accelerateEnabled = true;
|
||||
// 标记网络中是否能量不足(用于 GUI 提示)
|
||||
private boolean networkEnergyInsufficient = true;
|
||||
|
||||
|
||||
public boolean getAccelerateEnabled() {
|
||||
return this.accelerateEnabled;
|
||||
}
|
||||
|
||||
public boolean isNetworkEnergyInsufficient() {
|
||||
return this.networkEnergyInsufficient;
|
||||
}
|
||||
|
||||
public void setAccelerateEnabled(boolean accelerateEnabled) {
|
||||
this.accelerateEnabled = accelerateEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新网络能量不足标记并在菜单存在且状态变化时触发同步
|
||||
* @param insufficient 是否能量不足
|
||||
*/
|
||||
private void updateNetworkEnergyInsufficient(boolean insufficient) {
|
||||
if (this.networkEnergyInsufficient == insufficient) return;
|
||||
this.networkEnergyInsufficient = insufficient;
|
||||
if (this.menu != null) {
|
||||
try {
|
||||
this.menu.broadcastChanges();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态下的静态模型(用于渲染)
|
||||
* @return 当前状态的模型
|
||||
|
|
@ -239,11 +259,17 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
|
|||
// 先模拟提取以检查网络中是否有足够能量,再真正抽取
|
||||
double simulated = getMainNode().getGrid().getEnergyService()
|
||||
.extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG);
|
||||
if (simulated < requiredPower) return;
|
||||
if (simulated < requiredPower) {
|
||||
updateNetworkEnergyInsufficient(false);
|
||||
return;
|
||||
}
|
||||
|
||||
double extractedPower = getMainNode().getGrid().getEnergyService()
|
||||
.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG);
|
||||
if (extractedPower < requiredPower) return;
|
||||
if (extractedPower < requiredPower) {
|
||||
updateNetworkEnergyInsufficient(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算加速倍数:基于 2 的次方,并把 8 张映射到最大 1024x(2^10)
|
||||
// 已由 product 计算得到 speed;上面已在没有卡时提前返回
|
||||
|
|
|
|||
|
|
@ -123,10 +123,13 @@ public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends Up
|
|||
int energyCardCount = getMenu().energyCardCount;
|
||||
double multiplier = getMenu().multiplier;
|
||||
int effectiveSpeed = getMenu().effectiveSpeed;
|
||||
|
||||
double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount);
|
||||
double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount);
|
||||
|
||||
// 如果网络能量不足,优先显示警告信息并在能量值处显示 0
|
||||
if (getMenu().networkEnergyInsufficient) {
|
||||
setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient"));
|
||||
}
|
||||
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed));
|
||||
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false)));
|
||||
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio)));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.extendedae_plus.bridge;
|
||||
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.menu.ToolboxMenu;
|
||||
|
||||
public interface IUpgradableMenu {
|
||||
ToolboxMenu getToolbox();
|
||||
IUpgradeInventory getUpgrades();
|
||||
default boolean hasUpgrade(net.minecraft.world.level.ItemLike upgradeCard) {
|
||||
return getUpgrades().isInstalled(upgradeCard);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.extendedae_plus.bridge;
|
||||
|
||||
/**
|
||||
* 非 mixin 包下的桥接接口,供 mixin 进行 instanceof 检测和回调。
|
||||
*/
|
||||
public interface InterfaceWirelessLinkBridge {
|
||||
void eap$updateWirelessLink();
|
||||
|
||||
/**
|
||||
* 获取无线连接状态(服务端返回真实状态,客户端返回同步状态)
|
||||
*/
|
||||
default boolean eap$isWirelessConnected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端的无线连接状态(仅在客户端使用)
|
||||
*/
|
||||
default void eap$setClientWirelessState(boolean connected) {
|
||||
// 默认实现为空
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已经进行过tick初始化
|
||||
*/
|
||||
default boolean eap$hasTickInitialized() {
|
||||
return true; // 默认认为已初始化
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置tick初始化状态
|
||||
*/
|
||||
default void eap$setTickInitialized(boolean initialized) {
|
||||
// 默认实现为空
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行频道链接初始化
|
||||
*/
|
||||
default void eap$initializeChannelLink() {
|
||||
// 默认实现为空
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并处理延迟初始化
|
||||
*/
|
||||
default void eap$handleDelayedInit() {
|
||||
// 默认实现为空
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
|||
import net.minecraftforge.client.event.ScreenEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Optional;
|
||||
|
|
@ -28,88 +29,115 @@ public final class InputEvents {
|
|||
|
||||
@SubscribeEvent
|
||||
public static void onMouseButtonPre(ScreenEvent.MouseButtonPressed.Pre event) {
|
||||
// 若未安装 JEI,直接跳过,避免触发 JEI 类加载导致的 NoClassDefFoundError
|
||||
if (!ModList.get().isLoaded("jei")) {
|
||||
return;
|
||||
}
|
||||
// 若 JEI 运行时尚未就绪,跳过
|
||||
if (JeiRuntimeProxy.get() == null) {
|
||||
return;
|
||||
}
|
||||
// 优先处理:Shift + 左键(拉取或下单)
|
||||
if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) {
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY);
|
||||
if (hovered.isEmpty()) {
|
||||
hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
}
|
||||
if (hovered.isPresent()) {
|
||||
// 若 JEI 作弊模式开启,则放行给 JEI 处理(Shift+左键=一组)
|
||||
if (JeiRuntimeProxy.isJeiCheatModeEnabled()) {
|
||||
return;
|
||||
try {
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY);
|
||||
if (hovered.isEmpty()) {
|
||||
hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
}
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed);
|
||||
if (stack != null) {
|
||||
// 发送到服务端:若网络有库存则拉取一组到空槽,否则若可合成则打开下单界面
|
||||
ModNetwork.CHANNEL.sendToServer(new PullFromJeiOrCraftC2SPacket(stack));
|
||||
// 消费此次点击,避免 JEI/原版对左键的其它处理
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
if (hovered.isPresent()) {
|
||||
// 若 JEI 作弊模式开启,则放行给 JEI 处理(Shift+左键=一组)
|
||||
if (JeiRuntimeProxy.isJeiCheatModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed);
|
||||
if (stack != null) {
|
||||
// 发送到服务端:若网络有库存则拉取一组到空槽,否则若可合成则打开下单界面
|
||||
ModNetwork.CHANNEL.sendToServer(new PullFromJeiOrCraftC2SPacket(stack));
|
||||
// 消费此次点击,避免 JEI/原版对左键的其它处理
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// 兼容 JEI 版本差异或运行时异常
|
||||
}
|
||||
}
|
||||
|
||||
// 中键:打开 AE 下单界面(保持原有功能)
|
||||
if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) {
|
||||
// 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY);
|
||||
if (hovered.isEmpty()) {
|
||||
hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
try {
|
||||
// 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY);
|
||||
if (hovered.isEmpty()) {
|
||||
hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
}
|
||||
if (hovered.isEmpty()) return;
|
||||
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
// 若 JEI 作弊模式开启,则放行给 JEI 处理(中键=一组)
|
||||
if (JeiRuntimeProxy.isJeiCheatModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed);
|
||||
if (stack == null) return;
|
||||
|
||||
// 发送到服务端,让其验证并打开 CraftAmountMenu
|
||||
ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack));
|
||||
|
||||
// 消费此次点击,避免 JEI/原版对中键的其它处理
|
||||
event.setCanceled(true);
|
||||
} catch (Throwable ignored) {
|
||||
// 兼容 JEI 版本差异或运行时异常
|
||||
}
|
||||
if (hovered.isEmpty()) return;
|
||||
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
// 若 JEI 作弊模式开启,则放行给 JEI 处理(中键=一组)
|
||||
if (JeiRuntimeProxy.isJeiCheatModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed);
|
||||
if (stack == null) return;
|
||||
|
||||
// 发送到服务端,让其验证并打开 CraftAmountMenu
|
||||
ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack));
|
||||
|
||||
// 消费此次点击,避免 JEI/原版对中键的其它处理
|
||||
event.setCanceled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onKeyPressedPre(ScreenEvent.KeyPressed.Pre event) {
|
||||
// 若未安装 JEI,直接跳过
|
||||
if (!ModList.get().isLoaded("jei")) {
|
||||
return;
|
||||
}
|
||||
if (JeiRuntimeProxy.get() == null) {
|
||||
return;
|
||||
}
|
||||
if (event.getKeyCode() != GLFW.GLFW_KEY_F) return;
|
||||
|
||||
// 仅当鼠标确实悬停在 JEI 配料上时触发
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
if (hovered.isEmpty()) return;
|
||||
try {
|
||||
Optional<ITypedIngredient<?>> hovered = JeiRuntimeProxy.getIngredientUnderMouse();
|
||||
if (hovered.isEmpty()) return;
|
||||
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
ITypedIngredient<?> typed = hovered.get();
|
||||
|
||||
// 通用获取显示名称(兼容物品/流体等)
|
||||
String name = JeiRuntimeProxy.getTypedIngredientDisplayName(typed);
|
||||
if (name == null || name.isEmpty()) return;
|
||||
// 通用获取显示名称(兼容物品/流体等)
|
||||
String name = JeiRuntimeProxy.getTypedIngredientDisplayName(typed);
|
||||
if (name == null || name.isEmpty()) return;
|
||||
|
||||
// 写入 AE2 终端的搜索框
|
||||
var screen = Minecraft.getInstance().screen;
|
||||
if (screen instanceof MEStorageScreen<?> me) {
|
||||
try {
|
||||
MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me;
|
||||
acc.eap$getSearchField().setValue(name);
|
||||
acc.eap$setSearchText(name); // 同步到 Repo 并刷新
|
||||
event.setCanceled(true);
|
||||
} catch (Throwable ignored) {
|
||||
// 写入 AE2 终端的搜索框
|
||||
var screen = Minecraft.getInstance().screen;
|
||||
if (screen instanceof MEStorageScreen<?> me) {
|
||||
try {
|
||||
MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me;
|
||||
acc.eap$getSearchField().setValue(name);
|
||||
acc.eap$setSearchText(name); // 同步到 Repo 并刷新
|
||||
event.setCanceled(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}else if (screen instanceof GuiExPatternTerminal<?> gpt) {
|
||||
try {
|
||||
GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt;
|
||||
acc.getSearchOutField().setValue(name);
|
||||
event.setCanceled(true);
|
||||
}catch (Throwable ignored) {}
|
||||
}
|
||||
}else if (screen instanceof GuiExPatternTerminal<?> gpt) {
|
||||
try {
|
||||
GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt;
|
||||
acc.getSearchOutField().setValue(name);
|
||||
event.setCanceled(true);
|
||||
}catch (Throwable ignored) {}
|
||||
} catch (Throwable ignored) {
|
||||
// 兼容 JEI 版本差异或运行时异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
package com.extendedae_plus.command;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* /eap give_infinity_disks
|
||||
* 为执行命令的玩家生成当前世界已加载的所有 Infinity 磁盘(按 UUID)并发放到玩家物品栏
|
||||
*/
|
||||
public class InfinityDiskGiveCommand {
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(Commands.literal("eap").then(
|
||||
Commands.literal("give_infinity_disks").executes(InfinityDiskGiveCommand::execute)
|
||||
));
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<CommandSourceStack> ctx) {
|
||||
CommandSourceStack source = ctx.getSource();
|
||||
try {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
if (player.level() == null || !(player.level() instanceof ServerLevel)) {
|
||||
source.sendFailure(Component.literal("This command must be run on server side."));
|
||||
return 0;
|
||||
}
|
||||
InfinityStorageManager mgr = ExtendedAEPlus.STORAGE_INSTANCE;
|
||||
if (mgr == null) {
|
||||
source.sendFailure(Component.literal("InfinityStorageManager is not initialized."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int given = 0;
|
||||
for (UUID id : mgr.getAllLoadedUUIDs()) {
|
||||
ItemStack stack = InfinityBigIntegerCellItem.withUUID(id);
|
||||
if (!player.getInventory().add(stack)) {
|
||||
// 若玩家物品栏已满,则扔在地上
|
||||
player.drop(stack, false);
|
||||
}
|
||||
given++;
|
||||
}
|
||||
final int finalGiven = given;
|
||||
source.sendSuccess(() -> Component.literal("Gave " + finalGiven + " infinity disks."), false);
|
||||
return given;
|
||||
} catch (Exception ex) {
|
||||
source.sendFailure(Component.literal("Error: " + ex.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.extendedae_plus.compat;
|
||||
|
||||
import com.extendedae_plus.util.ExtendedAELogger;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
|
||||
/**
|
||||
* 兼容性测试类
|
||||
* 用于验证模组兼容性检测是否正常工作
|
||||
*/
|
||||
public class CompatibilityTest {
|
||||
|
||||
/**
|
||||
* 测试模组兼容性检测
|
||||
*/
|
||||
public static void testCompatibility() {
|
||||
ExtendedAELogger.LOGGER.info("=== ExtendedAE_Plus 兼容性测试开始 ===");
|
||||
|
||||
// 测试appflux模组检测
|
||||
boolean appfluxExists = ModList.get().isLoaded("appflux");
|
||||
ExtendedAELogger.LOGGER.info("ExtendedAE-appflux模组检测结果: {}", appfluxExists ? "存在" : "不存在");
|
||||
|
||||
// 测试升级卡槽功能启用状态
|
||||
boolean shouldEnableUpgrades = UpgradeSlotCompat.shouldEnableUpgradeSlots();
|
||||
ExtendedAELogger.LOGGER.info("升级卡槽功能启用状态: {}", shouldEnableUpgrades ? "启用" : "禁用");
|
||||
|
||||
// 测试Screen升级面板添加状态
|
||||
boolean shouldAddPanel = UpgradeSlotCompat.shouldAddUpgradePanelToScreen();
|
||||
ExtendedAELogger.LOGGER.info("Screen升级面板添加状态: {}", shouldAddPanel ? "启用" : "禁用");
|
||||
|
||||
// 输出兼容性策略
|
||||
if (appfluxExists) {
|
||||
ExtendedAELogger.LOGGER.info("兼容性策略: 检测到ExtendedAE-appflux模组,将使用其升级卡槽功能");
|
||||
} else {
|
||||
ExtendedAELogger.LOGGER.info("兼容性策略: 未检测到ExtendedAE-appflux模组,将使用我们自己的升级卡槽功能");
|
||||
}
|
||||
|
||||
ExtendedAELogger.LOGGER.info("=== ExtendedAE_Plus 兼容性测试完成 ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兼容性状态报告
|
||||
*/
|
||||
public static String getCompatibilityReport() {
|
||||
boolean appfluxExists = ModList.get().isLoaded("appflux");
|
||||
boolean upgradesEnabled = UpgradeSlotCompat.shouldEnableUpgradeSlots();
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append("ExtendedAE_Plus 兼容性报告:\n");
|
||||
report.append("- ExtendedAE-appflux模组: ").append(appfluxExists ? "已安装" : "未安装").append("\n");
|
||||
report.append("- 升级卡槽功能: ").append(upgradesEnabled ? "启用中" : "已禁用").append("\n");
|
||||
|
||||
if (appfluxExists && !upgradesEnabled) {
|
||||
report.append("- 兼容性状态: 正常 (使用appflux的升级功能)\n");
|
||||
} else if (!appfluxExists && upgradesEnabled) {
|
||||
report.append("- 兼容性状态: 正常 (使用我们的升级功能)\n");
|
||||
} else {
|
||||
report.append("- 兼容性状态: 异常 (配置不一致)\n");
|
||||
}
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
}
|
||||
219
src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java
Normal file
219
src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package com.extendedae_plus.compat;
|
||||
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.IUpgradeableObject;
|
||||
import appeng.api.upgrades.UpgradeInventories;
|
||||
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.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.SlotSemantics;
|
||||
import appeng.menu.ToolboxMenu;
|
||||
import com.extendedae_plus.util.ExtendedAELogger;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 升级卡槽兼容性管理类
|
||||
* 检测ExtendedAE-appflux模组是否存在,如果存在则使用其升级卡槽功能
|
||||
* 否则使用我们自己的实现
|
||||
*/
|
||||
public class UpgradeSlotCompat {
|
||||
private static final String APPFLUX_MOD_ID = "appflux";
|
||||
|
||||
/**
|
||||
* 检测Applied Flux模组是否存在
|
||||
* @return true如果存在,false如果不存在
|
||||
*/
|
||||
public static boolean isAppfluxPresent() {
|
||||
return ModList.get().isLoaded(APPFLUX_MOD_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否应该启用我们的升级卡槽功能
|
||||
* @return true如果应该启用,false如果检测到appflux模组存在
|
||||
*/
|
||||
public static boolean shouldEnableUpgradeSlots() {
|
||||
boolean appfluxExists = isAppfluxPresent();
|
||||
ExtendedAELogger.LOGGER.info("ExtendedAE-appflux模组检测: {}", appfluxExists ? "存在" : "不存在");
|
||||
|
||||
if (appfluxExists) {
|
||||
ExtendedAELogger.LOGGER.info("检测到ExtendedAE-appflux模组,跳过我们的升级卡槽功能");
|
||||
return false;
|
||||
} else {
|
||||
ExtendedAELogger.LOGGER.info("未检测到ExtendedAE-appflux模组,启用我们的升级卡槽功能");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否应该启用频道卡功能
|
||||
* 频道卡是我们独有的功能,即使appflux存在也应该启用
|
||||
* @return 总是返回true,因为频道卡功能不与appflux冲突
|
||||
*/
|
||||
public static boolean shouldEnableChannelCard() {
|
||||
return true; // 频道卡功能总是启用,因为appflux没有实现这个功能
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否应该在Screen中添加升级面板
|
||||
* @return true如果应该添加,false如果检测到appflux模组存在
|
||||
*/
|
||||
public static boolean shouldAddUpgradePanelToScreen() {
|
||||
return shouldEnableUpgradeSlots();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化菜单升级功能(如果需要的话)
|
||||
* @param menu 目标菜单
|
||||
* @param host 样板供应器逻辑主机
|
||||
* @return 是否成功初始化
|
||||
*/
|
||||
public static boolean initMenuUpgrades(AEBaseMenu menu, PatternProviderLogicHost host) {
|
||||
if (!shouldEnableUpgradeSlots()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建工具箱菜单
|
||||
ToolboxMenu toolbox = new ToolboxMenu(menu);
|
||||
|
||||
// 设置升级槽
|
||||
if (host instanceof IUpgradeableObject upgradeableHost) {
|
||||
// 使用反射调用protected的setupUpgrades方法
|
||||
try {
|
||||
var setupUpgradesMethod = AEBaseMenu.class.getDeclaredMethod("setupUpgrades", IUpgradeInventory.class);
|
||||
setupUpgradesMethod.setAccessible(true);
|
||||
setupUpgradesMethod.invoke(menu, upgradeableHost.getUpgrades());
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("反射调用setupUpgrades失败", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用反射或接口设置工具箱
|
||||
if (menu instanceof IUpgradeableMenuCompat compatMenu) {
|
||||
compatMenu.setCompatToolbox(toolbox);
|
||||
}
|
||||
|
||||
ExtendedAELogger.LOGGER.debug("成功为PatternProviderMenu初始化升级功能");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("初始化PatternProviderMenu升级功能时出错", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为Screen添加升级面板(如果需要的话)
|
||||
* @param widgets 小部件映射
|
||||
* @param menu 菜单实例
|
||||
* @param style 屏幕样式
|
||||
* @return 是否成功添加
|
||||
*/
|
||||
public static boolean addUpgradePanelToScreen(Object widgets, Object menu, ScreenStyle style) {
|
||||
if (!shouldAddUpgradePanelToScreen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (menu instanceof IUpgradeableMenuCompat compatMenu) {
|
||||
try {
|
||||
// 使用反射获取widgets的add方法 - 尝试不同的方法签名
|
||||
var widgetsClass = widgets.getClass();
|
||||
var addMethod = widgetsClass.getDeclaredMethod("add", String.class, Object.class);
|
||||
addMethod.setAccessible(true);
|
||||
|
||||
// 获取升级槽位
|
||||
var menuClass = menu.getClass();
|
||||
var getSlotsMethod = menuClass.getMethod("getSlots", SlotSemantics.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Slot> upgradeSlots = (List<Slot>) getSlotsMethod.invoke(menu, SlotSemantics.UPGRADE);
|
||||
|
||||
// 添加升级面板
|
||||
UpgradesPanel upgradesPanel = new UpgradesPanel(upgradeSlots, () -> getCompatibleUpgrades(compatMenu));
|
||||
addMethod.invoke(widgets, "upgrades", upgradesPanel);
|
||||
|
||||
// 添加工具箱面板(如果存在)
|
||||
ToolboxMenu toolbox = compatMenu.getCompatToolbox();
|
||||
if (toolbox != null && toolbox.isPresent()) {
|
||||
ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName());
|
||||
addMethod.invoke(widgets, "toolbox", toolboxPanel);
|
||||
}
|
||||
|
||||
ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板");
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// 尝试其他可能的方法签名
|
||||
try {
|
||||
var widgetsClass = widgets.getClass();
|
||||
var putMethod = widgetsClass.getDeclaredMethod("put", String.class, Object.class);
|
||||
putMethod.setAccessible(true);
|
||||
|
||||
// 获取升级槽位
|
||||
var menuClass = menu.getClass();
|
||||
var getSlotsMethod = menuClass.getMethod("getSlots", SlotSemantics.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Slot> upgradeSlots = (List<Slot>) getSlotsMethod.invoke(menu, SlotSemantics.UPGRADE);
|
||||
|
||||
// 添加升级面板
|
||||
UpgradesPanel upgradesPanel = new UpgradesPanel(upgradeSlots, () -> getCompatibleUpgrades(compatMenu));
|
||||
putMethod.invoke(widgets, "upgrades", upgradesPanel);
|
||||
|
||||
// 添加工具箱面板(如果存在)
|
||||
ToolboxMenu toolbox = compatMenu.getCompatToolbox();
|
||||
if (toolbox != null && toolbox.isPresent()) {
|
||||
ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName());
|
||||
putMethod.invoke(widgets, "toolbox", toolboxPanel);
|
||||
}
|
||||
|
||||
ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板(使用put方法)");
|
||||
return true;
|
||||
} catch (Exception e2) {
|
||||
ExtendedAELogger.LOGGER.error("反射调用widgets方法失败", e2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("为PatternProviderScreen添加升级面板时出错", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兼容的升级列表
|
||||
*/
|
||||
private static List<Component> getCompatibleUpgrades(IUpgradeableMenuCompat menu) {
|
||||
var list = new ArrayList<Component>();
|
||||
list.add(GuiText.CompatibleUpgrades.text());
|
||||
|
||||
try {
|
||||
IUpgradeInventory upgrades = menu.getCompatUpgrades();
|
||||
if (upgrades != null) {
|
||||
list.addAll(appeng.api.upgrades.Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("获取兼容升级列表时出错", e);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容性升级菜单接口
|
||||
*/
|
||||
public interface IUpgradeableMenuCompat {
|
||||
ToolboxMenu getCompatToolbox();
|
||||
void setCompatToolbox(ToolboxMenu toolbox);
|
||||
IUpgradeInventory getCompatUpgrades();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.extendedae_plus.content.matrix;
|
||||
|
||||
import com.glodblock.github.extendedae.common.blocks.matrix.BlockAssemblerMatrixBase;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
|
||||
/**
|
||||
* ExtendedAE_Plus: 装配矩阵上传核心方块(内部功能块)。
|
||||
* 仅用于作为多方块内部的“功能块”存在;是否允许自动上传由工具类检查集群中是否存在该核心决定。
|
||||
*/
|
||||
public class UploadCoreBlock extends BlockAssemblerMatrixBase<UploadCoreBlockEntity> {
|
||||
|
||||
public UploadCoreBlock() {
|
||||
super();
|
||||
}
|
||||
|
||||
public UploadCoreBlock(BlockBehaviour.Properties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getPresentItem() {
|
||||
// 由对应的 BlockItem 注册返回,上传核心不需要特殊的 PresentItem,可返回自身的 BlockItem
|
||||
return com.extendedae_plus.init.ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.extendedae_plus.content.matrix;
|
||||
|
||||
import com.glodblock.github.extendedae.common.me.matrix.ClusterAssemblerMatrix;
|
||||
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixFunction;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/**
|
||||
* ExtendedAE_Plus: 装配矩阵上传核心方块实体。
|
||||
* 作为矩阵内部功能块,仅用于标记该矩阵允许被自动上传(工具类会在集群中查找此实体)。
|
||||
*/
|
||||
public class UploadCoreBlockEntity extends TileAssemblerMatrixFunction {
|
||||
|
||||
public UploadCoreBlockEntity(BlockPos pos, BlockState state) {
|
||||
super((BlockEntityType<?>) com.extendedae_plus.init.ModBlockEntities.UPLOAD_CORE_BE.get(), pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ClusterAssemblerMatrix c) {
|
||||
// 无需修改集群,仅作为存在性标记。
|
||||
// 若后续需要限制为“最多一个”,可在 ExtendedAE_Plus 工具类或事件中做校验与提示。
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.extendedae_plus.init;
|
|||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
|
||||
import com.extendedae_plus.content.matrix.UploadCoreBlockEntity;
|
||||
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
|
|
@ -23,4 +24,10 @@ public final class ModBlockEntities {
|
|||
BLOCK_ENTITY_TYPES.register("network_pattern_controller",
|
||||
() -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new,
|
||||
ModBlocks.NETWORK_PATTERN_CONTROLLER.get()).build(null));
|
||||
|
||||
// 装配矩阵上传核心
|
||||
public static final RegistryObject<BlockEntityType<UploadCoreBlockEntity>> UPLOAD_CORE_BE =
|
||||
BLOCK_ENTITY_TYPES.register("assembler_matrix_upload_core",
|
||||
() -> BlockEntityType.Builder.of(UploadCoreBlockEntity::new,
|
||||
ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get()).build(null));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.extendedae_plus.init;
|
|||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlock;
|
||||
import com.extendedae_plus.content.matrix.UploadCoreBlock;
|
||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||
import appeng.block.crafting.CraftingUnitBlock;
|
||||
import appeng.blockentity.crafting.CraftingBlockEntity;
|
||||
|
|
@ -39,6 +40,16 @@ public final class ModBlocks {
|
|||
)
|
||||
);
|
||||
|
||||
// 装配矩阵上传核心(内部功能块)
|
||||
public static final RegistryObject<UploadCoreBlock> ASSEMBLER_MATRIX_UPLOAD_CORE = BLOCKS.register(
|
||||
"assembler_matrix_upload_core",
|
||||
() -> {
|
||||
var b = new UploadCoreBlock();
|
||||
// 注意:方块实体绑定延后到 commonSetup 的 enqueueWork 中执行,避免注册阶段循环依赖
|
||||
return b;
|
||||
}
|
||||
);
|
||||
|
||||
// Crafting Accelerators (reuse MAE2 textures/models)
|
||||
public static final RegistryObject<CraftingUnitBlock> ACCELERATOR_4x = BLOCKS.register(
|
||||
"4x_crafting_accelerator",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ public final class ModCreativeTabs {
|
|||
// 将本模组物品加入创造物品栏
|
||||
output.accept(ModItems.WIRELESS_TRANSCEIVER.get());
|
||||
output.accept(ModItems.NETWORK_PATTERN_CONTROLLER.get());
|
||||
// 装配矩阵上传核心
|
||||
output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get());
|
||||
output.accept(ModItems.ACCELERATOR_4x.get());
|
||||
output.accept(ModItems.ACCELERATOR_16x.get());
|
||||
output.accept(ModItems.ACCELERATOR_64x.get());
|
||||
|
|
@ -35,6 +37,9 @@ public final class ModCreativeTabs {
|
|||
output.accept(ModItems.createEntitySpeedCardStack(16));
|
||||
|
||||
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
|
||||
|
||||
// 频道卡
|
||||
output.accept(ModItems.CHANNEL_CARD.get());
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import appeng.api.parts.PartModels;
|
|||
import appeng.items.parts.PartModelsHelper;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||
import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
|
|
@ -28,6 +29,12 @@ public final class ModItems {
|
|||
() -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
// 装配矩阵上传核心(方块物品)
|
||||
public static final RegistryObject<Item> ASSEMBLER_MATRIX_UPLOAD_CORE = ITEMS.register(
|
||||
"assembler_matrix_upload_core",
|
||||
() -> new BlockItem(ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
// Crafting Accelerators
|
||||
public static final RegistryObject<Item> ACCELERATOR_4x = ITEMS.register(
|
||||
"4x_crafting_accelerator",
|
||||
|
|
@ -70,6 +77,12 @@ public final class ModItems {
|
|||
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
|
||||
);
|
||||
|
||||
// 频道卡(作为 AE 升级卡使用)
|
||||
public static final RegistryObject<ChannelCardItem> CHANNEL_CARD = ITEMS.register(
|
||||
"channel_card",
|
||||
() -> new ChannelCardItem(new Item.Properties())
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* 为 PartItem 注册 AE2 部件模型。
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
package com.extendedae_plus.init;
|
||||
|
||||
import appeng.api.upgrades.Upgrades;
|
||||
import appeng.core.definitions.AEBlocks;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import appeng.core.definitions.AEParts;
|
||||
import appeng.core.localization.GuiText;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER_PART;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE_PART;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE_PART;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_IMPORT_BUS;
|
||||
import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_EXPORT_BUS;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class UpgradeCards {
|
||||
public UpgradeCards(final FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
|
|
@ -12,6 +25,37 @@ public class UpgradeCards {
|
|||
Upgrades.add(AEItems.ENERGY_CARD, ModItems.ENTITY_TICKER_PART_ITEM.get(), 8, "group.entity_ticker.name");
|
||||
// 使用单一的 UpgradeCard Item 作为注册键,总共允许安装 4 张(不同等级由 ItemStack NBT 区分)
|
||||
Upgrades.add(ModItems.ENTITY_SPEED_CARD.get(), ModItems.ENTITY_TICKER_PART_ITEM.get(), 4, "group.entity_ticker.name");
|
||||
|
||||
// 新增:频道卡仅允许安装在 ME 接口(方块与部件)上,每台最多 1 张
|
||||
String interfaceGroup = GuiText.Interface.getTranslationKey();
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.INTERFACE, 1, interfaceGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.INTERFACE, 1, interfaceGroup);
|
||||
|
||||
// 新增:样板供应器(方块与部件)支持频道卡,每台最多 1 张
|
||||
String patternProviderGroup = "group.pattern_provider.name";
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||
|
||||
// ExtendedAE 的扩展样板供应器(方块与部件)
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(),EX_PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(),EX_PATTERN_PROVIDER_PART, 1, patternProviderGroup);
|
||||
|
||||
//EAE 的扩展接口与超大接口(方块与部件)支持频道卡
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_INTERFACE, 1, interfaceGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_INTERFACE_PART, 1, interfaceGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), OVERSIZE_INTERFACE, 1, interfaceGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), OVERSIZE_INTERFACE_PART, 1, interfaceGroup);
|
||||
|
||||
//AE2 的输入/输出/存储总线支持频道卡(部件)
|
||||
String ioBusGroup = GuiText.IOBuses.getTranslationKey();
|
||||
String storageGroup = "group.storage.name";
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.IMPORT_BUS, 1, ioBusGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.EXPORT_BUS, 1, ioBusGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.STORAGE_BUS, 1, storageGroup);
|
||||
|
||||
//EAE 的扩展输入/输出总线支持频道卡(部件)
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_IMPORT_BUS, 1, ioBusGroup);
|
||||
Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_EXPORT_BUS, 1, ioBusGroup);
|
||||
});
|
||||
}
|
||||
}
|
||||
114
src/main/java/com/extendedae_plus/mixin/MixinConditions.java
Normal file
114
src/main/java/com/extendedae_plus/mixin/MixinConditions.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package com.extendedae_plus.mixin;
|
||||
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Mixin条件加载插件
|
||||
* 用于根据模组存在情况动态加载不同的Mixin
|
||||
*/
|
||||
public class MixinConditions implements IMixinConfigPlugin {
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
// 初始化时调用
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefMapperConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||
// 对于升级相关的Mixin,检查appflux是否存在
|
||||
if (mixinClassName.contains("PatternProviderMenuUpgradesMixin") ||
|
||||
mixinClassName.contains("PatternProviderScreenUpgradesMixin") ||
|
||||
mixinClassName.contains("PatternProviderLogicUpgradesMixin") ||
|
||||
mixinClassName.contains("PatternProviderLogicHostUpgradesMixin")) {
|
||||
|
||||
try {
|
||||
// 检查ModList是否已初始化
|
||||
if (net.minecraftforge.fml.ModList.get() == null) {
|
||||
System.out.println("[ExtendedAE_Plus] ModList未初始化,默认应用升级Mixin: " + mixinClassName);
|
||||
return true; // 修改策略:未初始化时默认应用,运行时再检查
|
||||
}
|
||||
|
||||
boolean appfluxExists = net.minecraftforge.fml.ModList.get().isLoaded("appflux");
|
||||
boolean shouldApply = !appfluxExists;
|
||||
|
||||
System.out.println("[ExtendedAE_Plus] 升级Mixin检查: " + mixinClassName +
|
||||
", appflux存在: " + appfluxExists +
|
||||
", 应用Mixin: " + shouldApply);
|
||||
|
||||
return shouldApply;
|
||||
} catch (Exception e) {
|
||||
System.out.println("[ExtendedAE_Plus] ModList检查失败,默认应用升级Mixin: " + mixinClassName);
|
||||
return true; // 修改策略:出错时默认应用,运行时再检查
|
||||
}
|
||||
}
|
||||
|
||||
// 对于appflux相关的Mixin,总是加载但在运行时检查条件
|
||||
if (mixinClassName.contains("AppfluxPatternProviderLogicMixin")) {
|
||||
System.out.println("[ExtendedAE_Plus] 总是加载appflux Mixin,运行时检查条件: " + mixinClassName);
|
||||
return true; // 总是加载,在Mixin内部进行运行时检查
|
||||
}
|
||||
|
||||
// 对于InterfaceLogicUpgradesMixin,总是加载但在运行时检查条件
|
||||
if (mixinClassName.contains("InterfaceLogicUpgradesMixin")) {
|
||||
System.out.println("[ExtendedAE_Plus] 总是加载Interface升级Mixin,运行时检查条件: " + mixinClassName);
|
||||
return true; // 总是加载,在Mixin内部进行运行时检查
|
||||
}
|
||||
|
||||
// 对于CraftingCPUClusterMixin,检查MAE2是否存在
|
||||
if (mixinClassName.contains("CraftingCPUClusterMixin")) {
|
||||
try {
|
||||
// 检查ModList是否已初始化
|
||||
if (net.minecraftforge.fml.ModList.get() == null) {
|
||||
System.out.println("[ExtendedAE_Plus] ModList未初始化,默认应用CraftingCPU Mixin: " + mixinClassName);
|
||||
return true; // 未初始化时默认应用
|
||||
}
|
||||
|
||||
boolean mae2Exists = net.minecraftforge.fml.ModList.get().isLoaded("mae2");
|
||||
boolean shouldApply = !mae2Exists;
|
||||
|
||||
System.out.println("[ExtendedAE_Plus] CraftingCPU Mixin检查: " + mixinClassName +
|
||||
", MAE2存在: " + mae2Exists +
|
||||
", 应用Mixin: " + shouldApply);
|
||||
|
||||
return shouldApply;
|
||||
} catch (Exception e) {
|
||||
System.out.println("[ExtendedAE_Plus] ModList检查失败,默认跳过CraftingCPU Mixin: " + mixinClassName);
|
||||
return false; // 出错时默认跳过,避免冲突
|
||||
}
|
||||
}
|
||||
|
||||
// 其他Mixin正常应用
|
||||
System.out.println("[ExtendedAE_Plus] 加载Mixin: " + mixinClassName);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
|
||||
// 接受目标类
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preApply(String targetClassName, org.objectweb.asm.tree.ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
// 应用前调用
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postApply(String targetClassName, org.objectweb.asm.tree.ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
|
||||
// 应用后调用
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +94,6 @@ public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHol
|
|||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder
|
|||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import org.spongepowered.asm.mixin.injection.Redirect;
|
|||
public abstract class CraftingCPUClusterMixin {
|
||||
// 1) 提升“单方块线程上限”的常量,避免抛出 IAE 的 IllegalArgumentException
|
||||
@ModifyConstant(
|
||||
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V",
|
||||
constant = @Constant(intValue = 16)
|
||||
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V",
|
||||
constant = @Constant(intValue = 16)
|
||||
)
|
||||
private int extendedae_plus$raisePerUnitLimit(int original) {
|
||||
// 放宽到极大值,完全取消单方块 16 线程的硬限制
|
||||
|
|
@ -22,12 +22,12 @@ public abstract class CraftingCPUClusterMixin {
|
|||
|
||||
// 2) 保持统计使用原始线程值(若存在多处调用),不再返回固定 16
|
||||
@Redirect(
|
||||
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I",
|
||||
ordinal = 1
|
||||
)
|
||||
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I",
|
||||
ordinal = 1
|
||||
)
|
||||
)
|
||||
private int extendedae_plus$onGetThreadsForLimitCheck(CraftingBlockEntity te) {
|
||||
// 返回原始线程数,确保总并行单元不被错误下限
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.api.networking.IManagedGridNode;
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.UpgradeInventories;
|
||||
import appeng.helpers.InterfaceLogic;
|
||||
import appeng.helpers.InterfaceLogicHost;
|
||||
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||
import net.minecraft.world.item.Item;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
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.CallbackInfo;
|
||||
|
||||
/**
|
||||
* 为ME接口增加升级槽数量的Mixin
|
||||
* 兼容Applied Flux模组,避免冲突
|
||||
*/
|
||||
@Mixin(value = InterfaceLogic.class, remap = false, priority = 1100)
|
||||
public class InterfaceLogicUpgradesMixin {
|
||||
|
||||
@Final
|
||||
@Mutable
|
||||
@Shadow
|
||||
private IUpgradeInventory upgrades;
|
||||
|
||||
@Shadow
|
||||
protected void onUpgradesChanged() {}
|
||||
|
||||
/**
|
||||
* 在InterfaceLogic构造函数末尾注入,增加升级槽数量
|
||||
* 使用优先级1100确保在Applied Flux之后执行,但不会过度干扰其他组件
|
||||
*/
|
||||
@Inject(
|
||||
method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/InterfaceLogicHost;Lnet/minecraft/world/item/Item;I)V",
|
||||
at = @At("TAIL"),
|
||||
require = 0 // 设置为可选注入,避免在某些情况下导致崩溃
|
||||
)
|
||||
private void expandInterfaceUpgrades(IManagedGridNode gridNode, InterfaceLogicHost host, Item is, int slots, CallbackInfo ci) {
|
||||
try {
|
||||
// 安全检查
|
||||
if (this.upgrades == null || gridNode == null || host == null || is == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentSlots = this.upgrades.size();
|
||||
|
||||
// 检查Applied Flux是否已经修改了升级槽
|
||||
if (UpgradeSlotCompat.isAppfluxPresent()) {
|
||||
if (currentSlots >= 3) {
|
||||
// Applied Flux已经增加了足够的升级槽,跳过修改
|
||||
return;
|
||||
} else if (currentSlots == 2) {
|
||||
// Applied Flux增加到2个,我们再增加1个到3个
|
||||
this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged);
|
||||
} else if (currentSlots == 1) {
|
||||
// Applied Flux存在但未生效,直接增加到3个
|
||||
this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged);
|
||||
}
|
||||
} else {
|
||||
if (currentSlots == 1) {
|
||||
// Applied Flux不存在,将升级槽从1个增加到2个
|
||||
this.upgrades = UpgradeInventories.forMachine(is, 2, this::onUpgradesChanged);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 发生异常时不修改升级槽,确保不会崩溃
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
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 com.extendedae_plus.bridge.IUpgradableMenu;
|
||||
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;
|
||||
}
|
||||
|
||||
this.widgets.add("upgrades", new UpgradesPanel(
|
||||
menu.getSlots(SlotSemantics.UPGRADE),
|
||||
this::eap$getCompatibleUpgrades));
|
||||
if (((IUpgradableMenu) menu).getToolbox() != null && ((IUpgradableMenu) menu).getToolbox().isPresent()) {
|
||||
this.widgets.add("toolbox", new ToolboxPanel(style, ((IUpgradableMenu) menu).getToolbox().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private List<Component> eap$getCompatibleUpgrades() {
|
||||
var list = new ArrayList<Component>();
|
||||
list.add(GuiText.CompatibleUpgrades.text());
|
||||
list.addAll(Upgrades.getTooltipLinesForMachine(((IUpgradableMenu) menu).getUpgrades().getUpgradableItem()));
|
||||
return list;
|
||||
}
|
||||
|
||||
public PatternProviderScreenUpgradesMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super(menu, playerInventory, title, style);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.extendedae_plus.mixin.ae2.compat;
|
||||
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.ToolboxMenu;
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
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;
|
||||
|
||||
/**
|
||||
* PatternProviderMenu的兼容性Mixin
|
||||
* 优先级设置为500,低于appflux的默认优先级,避免冲突
|
||||
*/
|
||||
@Mixin(value = PatternProviderMenu.class, priority = 500, remap = false)
|
||||
public abstract class PatternProviderCompatMixin extends AEBaseMenu implements UpgradeSlotCompat.IUpgradeableMenuCompat {
|
||||
|
||||
@Unique
|
||||
private ToolboxMenu eap$compatToolbox;
|
||||
|
||||
@Unique
|
||||
private IUpgradeInventory eap$compatUpgrades;
|
||||
|
||||
@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$initCompatUpgrades(MenuType<?> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
// 检测是否应该启用升级卡槽功能
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
// 直接初始化升级功能
|
||||
this.eap$compatToolbox = new ToolboxMenu(this);
|
||||
|
||||
if (host instanceof appeng.api.upgrades.IUpgradeableObject upgradeableHost) {
|
||||
this.eap$compatUpgrades = upgradeableHost.getUpgrades();
|
||||
this.setupUpgrades(this.eap$compatUpgrades);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 静默处理异常,确保不会因为升级功能导致崩溃
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolboxMenu getCompatToolbox() {
|
||||
return this.eap$compatToolbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompatToolbox(ToolboxMenu toolbox) {
|
||||
this.eap$compatToolbox = toolbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IUpgradeInventory getCompatUpgrades() {
|
||||
return this.eap$compatUpgrades;
|
||||
}
|
||||
|
||||
// 构造函数,Mixin要求
|
||||
public PatternProviderCompatMixin(MenuType<?> menuType, int id, Inventory playerInventory, Object host) {
|
||||
super(menuType, id, playerInventory, host);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
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.compat.UpgradeSlotCompat;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||
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;
|
||||
|
||||
/**
|
||||
* PatternProviderLogic的兼容性Mixin
|
||||
* 优先级设置为1500,在appflux之后应用
|
||||
* 根据appflux是否存在来决定是否实现IUpgradeableObject接口
|
||||
*/
|
||||
@Mixin(value = PatternProviderLogic.class, priority = 500, remap = false)
|
||||
public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, 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;
|
||||
|
||||
@Unique
|
||||
private void eap$compatOnUpgradesChanged() {
|
||||
try {
|
||||
this.host.saveChanges();
|
||||
// 频道卡功能独立于升级槽功能,总是处理
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
// 升级变更,重置并尝试初始化频道卡
|
||||
eap$compatLastChannel = -1;
|
||||
eap$compatHasInitialized = false;
|
||||
eap$compatInitializeChannelLink();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级变更处理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听appflux的升级变化 - 通过注入到appflux的af_$onUpgradesChanged方法
|
||||
@Inject(method = "af_$onUpgradesChanged", at = @At("TAIL"), remap = false, require = 0)
|
||||
private void eap$onAppfluxUpgradesChanged(CallbackInfo ci) {
|
||||
try {
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("监听到appflux升级变化,处理频道卡");
|
||||
// 升级变更,重置并尝试初始化频道卡
|
||||
eap$compatLastChannel = -1;
|
||||
eap$compatHasInitialized = false;
|
||||
eap$compatInitializeChannelLink();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("监听appflux升级变化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V",
|
||||
at = @At("TAIL"))
|
||||
private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) {
|
||||
try {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("兼容性PatternProviderLogic初始化被调用");
|
||||
|
||||
boolean upgradeSlots = UpgradeSlotCompat.shouldEnableUpgradeSlots();
|
||||
boolean channelCard = UpgradeSlotCompat.shouldEnableChannelCard();
|
||||
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("升级槽功能: {}, 频道卡功能: {}", upgradeSlots, channelCard);
|
||||
|
||||
if (upgradeSlots) {
|
||||
// 只有在升级槽功能启用时才创建升级槽
|
||||
this.eap$compatUpgrades = UpgradeInventories.forMachine(
|
||||
host.getTerminalIcon().getItem(),
|
||||
1,
|
||||
this::eap$compatOnUpgradesChanged
|
||||
);
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("创建了完整的升级槽");
|
||||
} else if (channelCard) {
|
||||
// 如果装了appflux,我们不创建自己的升级槽,而是监听appflux的升级槽
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("装了appflux,将监听其升级槽来处理频道卡");
|
||||
} else {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("跳过升级槽创建");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$compatSaveUpgrades(CompoundTag tag, CallbackInfo ci) {
|
||||
try {
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级保存失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$compatLoadUpgrades(CompoundTag tag, CallbackInfo ci) {
|
||||
try {
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
this.eap$compatUpgrades.readFromNBT(tag, "compat_upgrades");
|
||||
// 从 NBT 加载后,重置并尝试初始化频道卡
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
eap$compatLastChannel = -1;
|
||||
eap$compatHasInitialized = false;
|
||||
eap$compatInitializeChannelLink();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级加载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "addDrops", at = @At("TAIL"))
|
||||
private void eap$compatDropUpgrades(List<ItemStack> drops, CallbackInfo ci) {
|
||||
try {
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
for (var stack : this.eap$compatUpgrades) {
|
||||
if (!stack.isEmpty()) {
|
||||
drops.add(stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级掉落失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "clearContent", at = @At("TAIL"))
|
||||
private void eap$compatClearUpgrades(CallbackInfo ci) {
|
||||
try {
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
this.eap$compatUpgrades.clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级清理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IUpgradeInventory getUpgrades() {
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
// 不装appflux时,返回我们自己的升级槽
|
||||
return this.eap$compatUpgrades != null ? this.eap$compatUpgrades : UpgradeInventories.empty();
|
||||
} else {
|
||||
// 装了appflux时,这个方法不应该被调用,因为appflux的Mixin会覆盖它
|
||||
// 但是为了安全起见,返回空的升级槽
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("装了appflux时getUpgrades被调用,这不应该发生");
|
||||
return UpgradeInventories.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$updateWirelessLink() {
|
||||
if (!UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (eap$compatLink != null) {
|
||||
eap$compatLink.updateStatus();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性无线链接更新失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
public void eap$compatInitializeChannelLink() {
|
||||
if (!UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 客户端早退
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免重复初始化
|
||||
if (eap$compatHasInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待网格完成引导
|
||||
if (!mainNode.hasGridBooted()) {
|
||||
eap$compatDelayedInitTicks = Math.max(eap$compatDelayedInitTicks, 5);
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||
});
|
||||
} catch (Throwable ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
long channel = 0L;
|
||||
boolean found = false;
|
||||
|
||||
// 获取升级槽 - 如果装了appflux则从appflux获取,否则从我们自己的获取
|
||||
IUpgradeInventory upgrades = null;
|
||||
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
// 不装appflux时使用我们自己的升级槽
|
||||
upgrades = this.eap$compatUpgrades;
|
||||
} else if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
// 装了appflux时,尝试从PatternProviderLogic获取升级槽
|
||||
try {
|
||||
if (this instanceof IUpgradeableObject) {
|
||||
IUpgradeableObject upgradeableThis = (IUpgradeableObject) this;
|
||||
upgrades = upgradeableThis.getUpgrades();
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("从appflux获取到升级槽: {}", upgrades != null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取appflux升级槽失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (upgrades != null) {
|
||||
for (ItemStack stack : upgrades) {
|
||||
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||
channel = ChannelCardItem.getChannel(stack);
|
||||
found = true;
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("找到频道卡,频道: {}", channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 无频道卡:断开并视为初始化完成
|
||||
if (eap$compatLink != null) {
|
||||
eap$compatLink.setFrequency(0L);
|
||||
eap$compatLink.updateStatus();
|
||||
}
|
||||
eap$compatHasInitialized = true;
|
||||
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();
|
||||
|
||||
if (eap$compatLink.isConnected()) {
|
||||
eap$compatHasInitialized = true;
|
||||
} else {
|
||||
eap$compatHasInitialized = false;
|
||||
eap$compatDelayedInitTicks = Math.max(eap$compatDelayedInitTicks, 5);
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||
});
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性频道链接初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setClientWirelessState(boolean connected) {
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
eap$compatClientConnected = connected;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$isWirelessConnected() {
|
||||
if (!UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return eap$compatClientConnected;
|
||||
} else {
|
||||
return eap$compatLink != null && eap$compatLink.isConnected();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("检查兼容性无线连接状态失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$hasTickInitialized() {
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return eap$compatHasInitialized;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setTickInitialized(boolean initialized) {
|
||||
if (UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
eap$compatHasInitialized = initialized;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$handleDelayedInit() {
|
||||
if (!UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 仅服务端
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
if (!eap$compatHasInitialized) {
|
||||
if (!mainNode.hasGridBooted()) {
|
||||
if (eap$compatDelayedInitTicks > 0) {
|
||||
eap$compatDelayedInitTicks--;
|
||||
}
|
||||
if (eap$compatDelayedInitTicks == 0) {
|
||||
eap$compatDelayedInitTicks = 5;
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||
});
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} else {
|
||||
eap$compatInitializeChannelLink();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性延迟初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onMainNodeStateChanged", at = @At("TAIL"))
|
||||
private void eap$compatOnMainNodeStateChangedTail(CallbackInfo ci) {
|
||||
if (!UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
eap$compatLastChannel = -1;
|
||||
eap$compatHasInitialized = false;
|
||||
eap$compatDelayedInitTicks = 10;
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||
});
|
||||
} catch (Throwable ignored) {}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性主节点状态变更处理失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.extendedae_plus.mixin.ae2.compat;
|
||||
|
||||
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.compat.UpgradeSlotCompat;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
/**
|
||||
* PatternProviderLogicHost的兼容性Mixin
|
||||
* 优先级设置为500,避免与appflux冲突
|
||||
*/
|
||||
@Mixin(value = PatternProviderLogicHost.class, priority = 500, remap = false)
|
||||
public interface PatternProviderLogicHostCompatMixin extends IUpgradeableObject {
|
||||
@Shadow PatternProviderLogic getLogic();
|
||||
|
||||
@Override
|
||||
default IUpgradeInventory getUpgrades() {
|
||||
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && !UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
return UpgradeInventories.empty();
|
||||
}
|
||||
return ((IUpgradeableObject) this.getLogic()).getUpgrades();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.extendedae_plus.mixin.ae2.compat;
|
||||
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* PatternProviderScreen的兼容性Mixin
|
||||
* 优先级设置为500,避免与appflux冲突
|
||||
*/
|
||||
@Mixin(value = PatternProviderScreen.class, priority = 500, remap = false)
|
||||
public abstract class PatternProviderScreenCompatMixin<C extends PatternProviderMenu> extends AEBaseScreen<C> {
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void eap$initCompatUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||
try {
|
||||
// 检测是否应该添加升级面板
|
||||
if (UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) {
|
||||
// 直接添加升级面板,不使用复杂的反射
|
||||
this.eap$addUpgradePanelDirect(menu, style);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 静默处理异常,确保不会因为升级功能导致崩溃
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderScreen兼容性升级面板初始化失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$addUpgradePanelDirect(PatternProviderMenu menu, ScreenStyle style) {
|
||||
try {
|
||||
// 直接添加升级面板
|
||||
this.widgets.add("upgrades", new UpgradesPanel(
|
||||
menu.getSlots(SlotSemantics.UPGRADE),
|
||||
this::eap$getCompatibleUpgrades));
|
||||
|
||||
// 添加工具箱面板(如果菜单实现了兼容接口)
|
||||
if (menu instanceof UpgradeSlotCompat.IUpgradeableMenuCompat compatMenu) {
|
||||
var toolbox = compatMenu.getCompatToolbox();
|
||||
if (toolbox != null && toolbox.isPresent()) {
|
||||
this.widgets.add("toolbox", new ToolboxPanel(style, toolbox.getName()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("直接添加升级面板失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private List<Component> eap$getCompatibleUpgrades() {
|
||||
var list = new ArrayList<Component>();
|
||||
list.add(GuiText.CompatibleUpgrades.text());
|
||||
|
||||
try {
|
||||
if (menu instanceof UpgradeSlotCompat.IUpgradeableMenuCompat compatMenu) {
|
||||
var upgrades = compatMenu.getCompatUpgrades();
|
||||
if (upgrades != null) {
|
||||
list.addAll(Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取兼容升级列表失败", e);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// 构造函数,Mixin要求
|
||||
public PatternProviderScreenCompatMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super(menu, playerInventory, title, style);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
package com.extendedae_plus.mixin.ae2.helpers;
|
||||
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.helpers.InterfaceLogic;
|
||||
import appeng.helpers.InterfaceLogicHost;
|
||||
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||
import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
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;
|
||||
|
||||
@Mixin(InterfaceLogic.class)
|
||||
public abstract class InterfaceLogicChannelCardMixin implements InterfaceWirelessLinkBridge {
|
||||
|
||||
@Shadow(remap = false) public abstract IUpgradeInventory getUpgrades();
|
||||
|
||||
@Shadow(remap = false) public abstract appeng.api.networking.IGridNode getActionableNode();
|
||||
|
||||
@Shadow(remap = false) protected InterfaceLogicHost host;
|
||||
|
||||
@Shadow(remap = false) protected appeng.api.networking.IManagedGridNode mainNode;
|
||||
|
||||
@Unique
|
||||
private WirelessSlaveLink eap$link;
|
||||
|
||||
@Unique
|
||||
private long eap$lastChannel = -1;
|
||||
|
||||
@Unique
|
||||
private boolean eap$clientConnected = false;
|
||||
|
||||
@Unique
|
||||
private boolean eap$hasInitialized = false;
|
||||
|
||||
@Unique
|
||||
private int eap$delayedInitTicks = 0;
|
||||
|
||||
static {
|
||||
// InterfaceLogicChannelCardMixin 已加载
|
||||
}
|
||||
|
||||
@Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false)
|
||||
private void eap$onUpgradesChangedTail(CallbackInfo ci) {
|
||||
// 升级变更时重置标志并尝试初始化
|
||||
eap$lastChannel = -1;
|
||||
eap$hasInitialized = false;
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
|
||||
@Inject(method = "gridChanged", at = @At("TAIL"), remap = false)
|
||||
private void eap$afterGridChanged(CallbackInfo ci) {
|
||||
// 网格状态变化时重置标志并设置延迟初始化
|
||||
eap$lastChannel = -1;
|
||||
eap$hasInitialized = false;
|
||||
eap$delayedInitTicks = 10; // 适当增加延迟tick,等待网格完成引导
|
||||
// 尝试唤醒设备,确保后续还能继续tick
|
||||
if (mainNode != null) {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try {
|
||||
grid.getTickManager().wakeDevice(node);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"), remap = false)
|
||||
private void eap$afterReadNBT(net.minecraft.nbt.CompoundTag tag, CallbackInfo ci) {
|
||||
// 从 NBT加载时重置标志
|
||||
eap$lastChannel = -1;
|
||||
eap$hasInitialized = false;
|
||||
// 直接尝试初始化
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
|
||||
@Inject(method = "clearContent", at = @At("HEAD"), remap = false)
|
||||
private void eap$onClearContent(CallbackInfo ci) {
|
||||
if (eap$link != null) {
|
||||
eap$link.onUnloadOrRemove();
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
public void eap$initializeChannelLink() {
|
||||
// 仅在服务端执行,避免在渲染线程/客户端触发任何初始化路径
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免重复初始化
|
||||
if (eap$hasInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先等待网格完成引导(比仅检查 isActive 更可靠)
|
||||
if (!mainNode.hasGridBooted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long channel = 0L;
|
||||
boolean found = false;
|
||||
for (ItemStack stack : getUpgrades()) {
|
||||
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||
channel = ChannelCardItem.getChannel(stack);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
// 无频道卡:断开并视为初始化完成
|
||||
if (eap$link != null) {
|
||||
eap$link.setFrequency(0L);
|
||||
eap$link.updateStatus();
|
||||
}
|
||||
eap$hasInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (eap$link == null) {
|
||||
var endpoint = new InterfaceNodeEndpointImpl(host, () -> this.mainNode.getNode());
|
||||
eap$link = new WirelessSlaveLink(endpoint);
|
||||
}
|
||||
|
||||
eap$link.setFrequency(channel);
|
||||
eap$link.updateStatus();
|
||||
|
||||
if (eap$link.isConnected()) {
|
||||
eap$hasInitialized = true; // 设置初始化完成标志
|
||||
} else {
|
||||
// 不标记为完成,允许后续tick重试
|
||||
eap$hasInitialized = false;
|
||||
// 设置一个短延迟窗口,避免每tick刷屏
|
||||
eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5);
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try {
|
||||
grid.getTickManager().wakeDevice(node);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$updateWirelessLink() {
|
||||
if (eap$link != null) {
|
||||
eap$link.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$isWirelessConnected() {
|
||||
// InterfaceLogic没有isClientSide方法,需要通过host判断
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return eap$clientConnected;
|
||||
} else {
|
||||
return eap$link != null && eap$link.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setClientWirelessState(boolean connected) {
|
||||
eap$clientConnected = connected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$hasTickInitialized() {
|
||||
return eap$hasInitialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setTickInitialized(boolean initialized) {
|
||||
eap$hasInitialized = initialized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$handleDelayedInit() {
|
||||
// 仅在服务端执行延迟初始化,避免在渲染线程/客户端触发任何初始化路径
|
||||
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 若尚未初始化,则持续尝试,直到网格完成引导
|
||||
if (!eap$hasInitialized) {
|
||||
if (!mainNode.hasGridBooted()) {
|
||||
// 仍在引导,消耗计时器
|
||||
if (eap$delayedInitTicks > 0) {
|
||||
eap$delayedInitTicks--;
|
||||
}
|
||||
if (eap$delayedInitTicks == 0) {
|
||||
// 重新设定一个短延迟窗口,并唤醒设备,以保证后续还能继续 tick
|
||||
eap$delayedInitTicks = 5;
|
||||
try {
|
||||
mainNode.ifPresent((grid, node) -> {
|
||||
try {
|
||||
grid.getTickManager().wakeDevice(node);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 网格已引导完成,执行初始化
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eap$initializeChannelLink方法已在上面实现
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.extendedae_plus.bridge;
|
||||
|
||||
/**
|
||||
* 旧名兼容:已迁移到非 mixin 包,避免 Mixin 处理器禁止直接引用。
|
||||
*/
|
||||
public interface InterfaceLogicChannelLinkBridge {
|
||||
void eap$updateWirelessLink();
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.extendedae_plus.mixin.ae2.helpers;
|
||||
|
||||
import appeng.helpers.InterfaceLogic;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
import com.extendedae_plus.util.ExtendedAELogger;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 注入到 InterfaceLogic.Ticker 的每tick回调,驱动无线链接状态更新。
|
||||
*/
|
||||
@Mixin(targets = "appeng.helpers.InterfaceLogic$Ticker")
|
||||
public abstract class InterfaceLogicTickerMixin {
|
||||
|
||||
// Mixin 访问内部类的外部引用字段(javac 生成名 this$0)
|
||||
@Shadow(remap = false)
|
||||
@Final
|
||||
private InterfaceLogic this$0;
|
||||
|
||||
@Inject(method = "tickingRequest", at = @At("HEAD"), remap = false)
|
||||
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"), remap = false)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,7 +94,6 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
|||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
|||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
// Mixin 访问内部类的外部引用字段(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.extendedae_plus.mixin.ae2.menu;
|
||||
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.IUpgradeableObject;
|
||||
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 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;
|
||||
|
||||
@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) {
|
||||
// 只有在应该启用升级卡槽时才初始化
|
||||
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eap$toolbox = new ToolboxMenu(this);
|
||||
this.setupUpgrades(((IUpgradeableObject) host).getUpgrades());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolboxMenu getToolbox() {
|
||||
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
return null;
|
||||
}
|
||||
return this.eap$toolbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IUpgradeInventory getUpgrades() {
|
||||
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||
return appeng.api.upgrades.UpgradeInventories.empty();
|
||||
}
|
||||
return ((IUpgradeableObject) this.logic).getUpgrades();
|
||||
}
|
||||
|
||||
public PatternProviderMenuUpgradesMixin(MenuType<?> menuType, int id, Inventory playerInventory, Object host) {
|
||||
super(menuType, id, playerInventory, host);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.extendedae_plus.mixin.ae2.parts;
|
||||
|
||||
import appeng.parts.AEBasePart;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
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.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
/**
|
||||
* 为所有AE2 Part添加无线连接状态的客户端同步功能
|
||||
*/
|
||||
@Mixin(value = AEBasePart.class, remap = false)
|
||||
public class AEBasePartClientSyncMixin {
|
||||
|
||||
@Inject(method = "writeToStream", at = @At("TAIL"))
|
||||
private void eap$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) {
|
||||
// 检查是否实现了无线链接桥接接口
|
||||
if (this instanceof InterfaceWirelessLinkBridge) {
|
||||
InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this;
|
||||
// 同步无线连接状态到客户端
|
||||
boolean connected = false;
|
||||
try {
|
||||
// 只在服务端获取真实连接状态
|
||||
AEBasePart part = (AEBasePart)(Object)this;
|
||||
if (!part.isClientSide()) {
|
||||
connected = bridge.eap$isWirelessConnected();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略异常,默认为false
|
||||
}
|
||||
data.writeBoolean(connected);
|
||||
} else {
|
||||
// 不是无线链接Part,写入false
|
||||
data.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readFromStream", at = @At("TAIL"))
|
||||
private void eap$readWirelessState(FriendlyByteBuf data, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 读取无线连接状态
|
||||
boolean connected = data.readBoolean();
|
||||
|
||||
// 检查是否实现了无线链接桥接接口
|
||||
if (this instanceof InterfaceWirelessLinkBridge) {
|
||||
InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this;
|
||||
try {
|
||||
// 更新客户端状态
|
||||
bridge.eap$setClientWirelessState(connected);
|
||||
} catch (Exception e) {
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
package com.extendedae_plus.mixin.ae2.parts.automation;
|
||||
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.IUpgradeableObject;
|
||||
import appeng.helpers.InterfaceLogicHost;
|
||||
import appeng.parts.automation.IOBusPart;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import com.extendedae_plus.util.ExtendedAELogger;
|
||||
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 给 AE2 的 I/O 总线注入频道卡联动:在升级变更时读取频道并更新无线链接。
|
||||
*/
|
||||
@Mixin(value = IOBusPart.class, remap = false)
|
||||
public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject {
|
||||
|
||||
@Unique
|
||||
private WirelessSlaveLink eap$link;
|
||||
|
||||
@Unique
|
||||
private long eap$lastChannel = -1;
|
||||
|
||||
@Unique
|
||||
private boolean eap$clientConnected = false;
|
||||
|
||||
@Unique
|
||||
private boolean eap$hasTickInitialized = false;
|
||||
|
||||
@Inject(method = "upgradesChanged", at = @At("TAIL"))
|
||||
private void eap$onUpgradesChanged(CallbackInfo ci) {
|
||||
// 只在服务端初始化频道链接
|
||||
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "tickingRequest", at = @At("HEAD"))
|
||||
private void eap$beforeTick(appeng.api.networking.IGridNode node, int ticksSinceLastCall, org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||
// 在第一次tick时初始化频道链接(此时网格节点已经在线)
|
||||
if (!eap$hasTickInitialized && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
eap$hasTickInitialized = true;
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) {
|
||||
// 从NBT加载时重置频道缓存和tick初始化标志
|
||||
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
eap$lastChannel = -1;
|
||||
eap$hasTickInitialized = false; // 重置标志,允许再次初始化
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
public void eap$initializeChannelLink() {
|
||||
// 防止重复调用
|
||||
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
IUpgradeInventory inv = this.getUpgrades();
|
||||
long channel = 0L;
|
||||
boolean found = false;
|
||||
for (var stack : inv) {
|
||||
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||
channel = ChannelCardItem.getChannel(stack);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 频道没有变化则跳过
|
||||
if (eap$lastChannel == channel) {
|
||||
return;
|
||||
}
|
||||
eap$lastChannel = channel;
|
||||
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel);
|
||||
|
||||
if (!found) {
|
||||
// 无频道卡则断开
|
||||
if (eap$link != null) {
|
||||
eap$link.setFrequency(0L);
|
||||
eap$link.updateStatus();
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 断开频道链接");
|
||||
// 立即通知客户端状态变化(断开连接无需延迟)
|
||||
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (eap$link == null) {
|
||||
var endpoint = new GenericNodeEndpointImpl(
|
||||
() -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(),
|
||||
() -> ((IActionHost)(Object)this).getActionableNode()
|
||||
);
|
||||
eap$link = new WirelessSlaveLink(endpoint);
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 创建新的无线链接");
|
||||
}
|
||||
|
||||
eap$link.setFrequency(channel);
|
||||
eap$link.updateStatus();
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected());
|
||||
|
||||
// 通知客户端状态变化
|
||||
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("[服务端] IOBus 初始化频道链接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$updateWirelessLink() {
|
||||
if (eap$link != null) {
|
||||
eap$link.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$isWirelessConnected() {
|
||||
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
return eap$clientConnected;
|
||||
} else {
|
||||
return eap$link != null && eap$link.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setClientWirelessState(boolean connected) {
|
||||
eap$clientConnected = connected;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.extendedae_plus.mixin.ae2.parts.automation;
|
||||
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.ticking.TickRateModulation;
|
||||
import appeng.parts.automation.IOBusPart;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 在 I/O 总线的 tickingRequest 尾部驱动无线链接刷新。
|
||||
*/
|
||||
@Mixin(value = IOBusPart.class, remap = false)
|
||||
public abstract class IOBusPartTickerChannelCardMixin {
|
||||
|
||||
@Inject(method = "tickingRequest", at = @At("TAIL"))
|
||||
private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) {
|
||||
if (this instanceof InterfaceWirelessLinkBridge bridge) {
|
||||
bridge.eap$updateWirelessLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package com.extendedae_plus.mixin.ae2.parts.storagebus;
|
||||
|
||||
import appeng.api.networking.IGridNodeListener;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.IUpgradeableObject;
|
||||
import appeng.parts.storagebus.StorageBusPart;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import com.extendedae_plus.util.ExtendedAELogger;
|
||||
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 给 AE2 的存储总线注入频道卡联动:在升级变更时读取频道并更新无线链接。
|
||||
*/
|
||||
@Mixin(value = StorageBusPart.class, remap = false)
|
||||
public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject {
|
||||
|
||||
@Unique
|
||||
private WirelessSlaveLink eap$link;
|
||||
|
||||
@Unique
|
||||
private long eap$lastChannel = -1;
|
||||
|
||||
@Unique
|
||||
private boolean eap$clientConnected = false;
|
||||
|
||||
@Inject(method = "upgradesChanged", at = @At("TAIL"))
|
||||
private void eap$onUpgradesChanged(CallbackInfo ci) {
|
||||
// 只在服务端初始化频道链接
|
||||
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onMainNodeStateChanged", at = @At("TAIL"))
|
||||
private void eap$onMainNodeStateChanged(IGridNodeListener.State reason, CallbackInfo ci) {
|
||||
// 在节点状态变化时(包括加载后的GRID_BOOT)重新初始化频道链接
|
||||
if (reason == IGridNodeListener.State.GRID_BOOT && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) {
|
||||
// 从NBT加载后也重新初始化频道链接(只在服务端)
|
||||
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
// 从NBT加载时重置频道缓存,强制重新初始化
|
||||
eap$lastChannel = -1;
|
||||
eap$initializeChannelLink();
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
public void eap$initializeChannelLink() {
|
||||
// 防止重复调用
|
||||
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
IUpgradeInventory inv = this.getUpgrades();
|
||||
long channel = 0L;
|
||||
boolean found = false;
|
||||
for (var stack : inv) {
|
||||
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||
channel = ChannelCardItem.getChannel(stack);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 频道没有变化则跳过
|
||||
if (eap$lastChannel == channel) {
|
||||
return;
|
||||
}
|
||||
eap$lastChannel = channel;
|
||||
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel);
|
||||
|
||||
if (!found) {
|
||||
if (eap$link != null) {
|
||||
eap$link.setFrequency(0L);
|
||||
eap$link.updateStatus();
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 断开频道链接");
|
||||
// 通知客户端状态变化
|
||||
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (eap$link == null) {
|
||||
var endpoint = new GenericNodeEndpointImpl(
|
||||
() -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(),
|
||||
() -> ((IActionHost)(Object)this).getActionableNode()
|
||||
);
|
||||
eap$link = new WirelessSlaveLink(endpoint);
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 创建新的无线链接");
|
||||
}
|
||||
|
||||
eap$link.setFrequency(channel);
|
||||
eap$link.updateStatus();
|
||||
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected());
|
||||
|
||||
// 通知客户端状态变化
|
||||
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||
} catch (Exception e) {
|
||||
ExtendedAELogger.LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$updateWirelessLink() {
|
||||
if (eap$link != null) {
|
||||
eap$link.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$isWirelessConnected() {
|
||||
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||
return eap$clientConnected;
|
||||
} else {
|
||||
return eap$link != null && eap$link.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setClientWirelessState(boolean connected) {
|
||||
eap$clientConnected = connected;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.extendedae_plus.mixin.ae2.parts.storagebus;
|
||||
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.ticking.TickRateModulation;
|
||||
import appeng.parts.storagebus.StorageBusPart;
|
||||
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 在存储总线的 tickingRequest 尾部驱动无线链接刷新。
|
||||
*/
|
||||
@Mixin(value = StorageBusPart.class, remap = false)
|
||||
public abstract class StorageBusPartTickerChannelCardMixin {
|
||||
|
||||
@Inject(method = "tickingRequest", at = @At("TAIL"))
|
||||
private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) {
|
||||
if (this instanceof InterfaceWirelessLinkBridge bridge) {
|
||||
bridge.eap$updateWirelessLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package com.extendedae_plus.mixin.appflux;
|
||||
|
||||
import appeng.api.networking.IManagedGridNode;
|
||||
import appeng.api.upgrades.IUpgradeInventory;
|
||||
import appeng.api.upgrades.UpgradeInventories;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||
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.CallbackInfo;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 当appflux存在时,修改PatternProviderLogic的升级槽数量为2个
|
||||
* 优先级设置为2000,确保在appflux之后应用
|
||||
*/
|
||||
@Mixin(value = PatternProviderLogic.class, priority = 2000, remap = false)
|
||||
public class AppfluxPatternProviderLogicMixin {
|
||||
|
||||
/**
|
||||
* 在appflux初始化升级槽之后,替换为2个槽的版本
|
||||
*/
|
||||
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V",
|
||||
at = @At("TAIL"))
|
||||
private void eap$modifyAppfluxUpgradeSlots(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) {
|
||||
try {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("AppfluxPatternProviderLogicMixin被调用!");
|
||||
|
||||
// 只有当appflux存在且不启用我们的升级槽时才修改数量
|
||||
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && UpgradeSlotCompat.shouldEnableChannelCard()) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("尝试修改appflux升级槽数量为2个");
|
||||
|
||||
// 使用反射找到appflux的升级槽字段并替换
|
||||
try {
|
||||
Field upgradesField = this.getClass().getDeclaredField("af_$upgrades");
|
||||
upgradesField.setAccessible(true);
|
||||
IUpgradeInventory currentUpgrades = (IUpgradeInventory) upgradesField.get(this);
|
||||
|
||||
if (currentUpgrades != null) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("找到appflux升级槽,当前大小: {}", currentUpgrades.size());
|
||||
|
||||
// 创建新的2槽升级槽
|
||||
IUpgradeInventory newUpgrades = UpgradeInventories.forMachine(
|
||||
host.getTerminalIcon().getItem(),
|
||||
2,
|
||||
() -> {
|
||||
try {
|
||||
// 调用appflux的升级变更方法
|
||||
this.getClass().getDeclaredMethod("af_$onUpgradesChanged").invoke(this);
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("调用appflux升级变更方法失败", e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 复制原有升级卡到新的升级槽
|
||||
for (int i = 0; i < Math.min(currentUpgrades.size(), newUpgrades.size()); i++) {
|
||||
if (!currentUpgrades.getStackInSlot(i).isEmpty()) {
|
||||
newUpgrades.insertItem(i, currentUpgrades.getStackInSlot(i).copy(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// 替换升级槽
|
||||
upgradesField.set(this, newUpgrades);
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("成功将appflux升级槽替换为2个槽");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("反射修改appflux升级槽失败", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ import com.extendedae_plus.hooks.BuiltInModelHooks;
|
|||
@Mixin(ModelBakery.class)
|
||||
public class ModelBakeryMixin {
|
||||
@Inject(method = "loadModel", at = @At("HEAD"), cancellable = true)
|
||||
private void extendedae_plus$loadModelHook(ResourceLocation id, CallbackInfo ci) {
|
||||
private void eap$loadModelHook(ResourceLocation id, CallbackInfo ci) {
|
||||
var model = BuiltInModelHooks.getBuiltInModel(id);
|
||||
if (model != null) {
|
||||
cacheAndQueueDependencies(id, model);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
public abstract class EncodePatternTransferHandlerMixin {
|
||||
|
||||
@Inject(method = "transferRecipe", at = @At("HEAD"), require = 0)
|
||||
private void extendedae_plus$captureProcessingName(PatternEncodingTermMenu menu,
|
||||
private void eap$captureProcessingName(PatternEncodingTermMenu menu,
|
||||
Object recipeBase,
|
||||
IRecipeSlotsView slotsView,
|
||||
Player player,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
|
@ -114,34 +115,71 @@ public final class ConfigParsingUtils {
|
|||
// ------------------ 全局缓存与接口 ------------------
|
||||
private static volatile List<Pattern> cachedBlacklist = null;
|
||||
private static volatile List<MultiplierEntry> cachedMultiplierEntries = null;
|
||||
private static volatile List<String> cachedBlacklistSourceSnapshot = null;
|
||||
private static volatile List<String> cachedMultiplierSourceSnapshot = null;
|
||||
private static final Object CACHE_LOCK = new Object();
|
||||
|
||||
/**
|
||||
* 获取已解析并缓存的黑名单(线程安全、懒加载)。
|
||||
*/
|
||||
public static List<Pattern> getCachedBlacklist(java.util.List<? extends String> source) {
|
||||
if (cachedBlacklist == null) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedBlacklist == null) {
|
||||
cachedBlacklist = compilePatterns(source);
|
||||
}
|
||||
}
|
||||
List<String> normalized = normalizeSource(source);
|
||||
|
||||
// fast path: identical snapshot reference or equal contents
|
||||
if (cachedBlacklist != null && listEquals(cachedBlacklistSourceSnapshot, normalized)) {
|
||||
return Collections.unmodifiableList(cachedBlacklist);
|
||||
}
|
||||
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedBlacklist == null || !listEquals(cachedBlacklistSourceSnapshot, normalized)) {
|
||||
cachedBlacklist = compilePatterns(normalized);
|
||||
cachedBlacklistSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized);
|
||||
}
|
||||
return Collections.unmodifiableList(cachedBlacklist);
|
||||
}
|
||||
return java.util.List.copyOf(cachedBlacklist);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已解析并缓存的倍率列表(线程安全、懒加载)。
|
||||
*/
|
||||
public static List<MultiplierEntry> getCachedMultiplierEntries(java.util.List<? extends String> source) {
|
||||
if (cachedMultiplierEntries == null) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedMultiplierEntries == null) {
|
||||
cachedMultiplierEntries = parseMultiplierList(source);
|
||||
}
|
||||
}
|
||||
List<String> normalized = normalizeSource(source);
|
||||
|
||||
if (cachedMultiplierEntries != null && listEquals(cachedMultiplierSourceSnapshot, normalized)) {
|
||||
return Collections.unmodifiableList(cachedMultiplierEntries);
|
||||
}
|
||||
return java.util.List.copyOf(cachedMultiplierEntries);
|
||||
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedMultiplierEntries == null || !listEquals(cachedMultiplierSourceSnapshot, normalized)) {
|
||||
cachedMultiplierEntries = parseMultiplierList(normalized);
|
||||
cachedMultiplierSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized);
|
||||
}
|
||||
return Collections.unmodifiableList(cachedMultiplierEntries);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the incoming source list: trim entries, drop blanks, keep stable ordering
|
||||
private static List<String> normalizeSource(java.util.List<? extends String> source) {
|
||||
List<String> out = new ArrayList<>();
|
||||
if (source == null) return out;
|
||||
for (String s : source) {
|
||||
if (s == null) continue;
|
||||
String t = s.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
out.add(t);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Null-safe equality for two lists of strings. Uses size + element equals.
|
||||
private static boolean listEquals(List<String> a, List<String> b) {
|
||||
if (a == b) return true;
|
||||
if (a == null || b == null) return false;
|
||||
if (a.size() != b.size()) return false;
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
if (!a.get(i).equals(b.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -543,7 +543,7 @@ public class ExtendedAEPatternUploadUtil {
|
|||
var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class);
|
||||
int idx = 0;
|
||||
for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) {
|
||||
if (tile != null && tile.isFormed() && tile.getMainNode().isActive()) {
|
||||
if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) {
|
||||
var inv = tile.getExposedInventory();
|
||||
if (inv != null) {
|
||||
result.add(inv);
|
||||
|
|
@ -565,7 +565,7 @@ public class ExtendedAEPatternUploadUtil {
|
|||
Set<TileAssemblerMatrixBase> matrices = grid.getMachines(TileAssemblerMatrixBase.class);
|
||||
int idx = 0;
|
||||
for (TileAssemblerMatrixBase tile : matrices) {
|
||||
if (tile != null && tile.isFormed()) {
|
||||
if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) {
|
||||
var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null);
|
||||
if (capOpt != null) {
|
||||
var handler = capOpt.orElse(null);
|
||||
|
|
@ -631,6 +631,28 @@ public class ExtendedAEPatternUploadUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定矩阵集群中是否存在“装配矩阵上传核心”。
|
||||
* 要求:至少存在 1 个即可,不限制数量。
|
||||
* 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。
|
||||
*/
|
||||
private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) {
|
||||
try {
|
||||
if (any == null || any.getCluster() == null) return false;
|
||||
int cores = 0;
|
||||
var it = any.getCluster().getBlockEntities();
|
||||
while (it.hasNext()) {
|
||||
var te = it.next();
|
||||
if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) {
|
||||
cores++;
|
||||
}
|
||||
}
|
||||
return cores >= 1; // 至少一个即可
|
||||
} catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前菜单是否为ExtendedAE的扩展样板管理终端
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.extendedae_plus.util.storage;
|
||||
|
||||
public interface InfinityConstants {
|
||||
// 当前磁盘格式版本号,增加字段用于向后/向前兼容
|
||||
int FORMAT_VERSION = 2;
|
||||
// 存储磁盘数据的格式版本号
|
||||
String FORMAT_VERSION_FIELD = "infinity_format_version";
|
||||
|
||||
// savedData 文件名常量
|
||||
String SAVE_FILE_NAME = "infinity_biginteger_cells";
|
||||
|
||||
// 磁盘的唯一标识符键名,存储在 ItemStack 和 InfinityStorageManager 中
|
||||
String INFINITY_CELL_UUID = "infinity_cell_uuid";
|
||||
// 单个磁盘的 InfinityDataStorage 数据键名
|
||||
String INFINITY_CELL_DATA = "infinity_cell_data";
|
||||
// 所有磁盘数据的列表键名,存储在 InfinityStorageManager 的 NBT 中
|
||||
String INFINITY_CELL_LIST = "infinity_cell_list";
|
||||
|
||||
// 磁盘中所有物品键的键名(ListTag of CompoundTag)
|
||||
String INFINITY_CELL_KEYS = "infinity_cell_keys";
|
||||
// 磁盘中每种物品数量的键名(ListTag of CompoundTag,包含 "value")
|
||||
String INFINITY_CELL_AMOUNTS = "infinity_cell_amounts";
|
||||
// 磁盘中所有物品的总数键名(ListTag,包含一个 CompoundTag 的 "value")
|
||||
String INFINITY_CELL_ITEM_COUNT = "infinity_cell_item_count";
|
||||
|
||||
// ItemStack 的 NBT 中存储总物品数量的键名
|
||||
String INFINITY_ITEM_TOTAL = "infinity_item_total";
|
||||
// ItemStack 的 NBT 中存储物品种类数量的键名
|
||||
String INFINITY_ITEM_TYPES = "infinity_item_types";
|
||||
}
|
||||
|
|
@ -2,60 +2,47 @@ package com.extendedae_plus.util.storage;
|
|||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* InfinityDataStorage
|
||||
*
|
||||
* 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。
|
||||
* 数据结构说明:
|
||||
* - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目
|
||||
* - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示:
|
||||
* - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值
|
||||
* - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式
|
||||
*
|
||||
* 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。
|
||||
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||
* Original copyright (c) Technici4n<p>
|
||||
*/
|
||||
public class InfinityDataStorage {
|
||||
|
||||
/** 空实例(表示没有数据) */
|
||||
// 定义一个静态常量 EMPTY,表示一个空的 DataStorage 实例,用于默认或占位场景
|
||||
public static final InfinityDataStorage EMPTY = new InfinityDataStorage();
|
||||
|
||||
/** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */
|
||||
public ListTag keys;
|
||||
/**
|
||||
* 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag):
|
||||
* - 若数量能放入 long,则 CompoundTag 包含键 "l"(long)
|
||||
* - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式
|
||||
*/
|
||||
public ListTag amounts;
|
||||
// 存储磁盘中物品的总数,使用 BigInteger 支持大容量
|
||||
public BigInteger itemCount;
|
||||
|
||||
public InfinityDataStorage() {
|
||||
this(new ListTag(), new ListTag());
|
||||
this(new ListTag(), new ListTag(), BigInteger.ZERO);
|
||||
}
|
||||
|
||||
private InfinityDataStorage(ListTag keys, ListTag amounts) {
|
||||
private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) {
|
||||
this.keys = keys;
|
||||
this.amounts = amounts;
|
||||
this.itemCount = itemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前数据封装为 CompoundTag 以写入存档
|
||||
*/
|
||||
// 将 DataStorage 数据序列化为 NBT 格式
|
||||
public CompoundTag serializeNBT() {
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
nbt.put("keys", keys);
|
||||
nbt.put("amounts", amounts);
|
||||
nbt.put(InfinityConstants.INFINITY_CELL_KEYS, keys);
|
||||
nbt.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts);
|
||||
nbt.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray());
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存档读取数据并构造实例
|
||||
*/
|
||||
// 从 NBT 数据反序列化创建 DataStorage 实例
|
||||
public static InfinityDataStorage loadFromNBT(CompoundTag nbt) {
|
||||
ListTag stackKeys = nbt.getList("keys", Tag.TAG_COMPOUND);
|
||||
// amounts 以 CompoundTag 列表存储,每个 CompoundTag 内含 long 或 String
|
||||
ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND);
|
||||
return new InfinityDataStorage(stackKeys, stackAmounts);
|
||||
ListTag keys = nbt.getList(InfinityConstants.INFINITY_CELL_KEYS, ListTag.TAG_COMPOUND);
|
||||
ListTag amounts = nbt.getList(InfinityConstants.INFINITY_CELL_AMOUNTS, ListTag.TAG_COMPOUND);
|
||||
BigInteger itemCount = new BigInteger(nbt.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT));
|
||||
// 使用加载的数据创建新的 DataStorage 实例
|
||||
return new InfinityDataStorage(keys, amounts, itemCount);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,62 +2,37 @@ package com.extendedae_plus.util.storage;
|
|||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* InfinityStorageManager
|
||||
* <p>
|
||||
* 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
|
||||
* 功能要点:
|
||||
* - 在世界加载时从存档恢复所有 cell 的数据
|
||||
* - 提供按 UUID 获取/创建单个 cell 的数据容器
|
||||
* - 在世界保存时将内存数据打包为 NBT 写回存档
|
||||
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||
* Original copyright (c) Technici4n<p>
|
||||
*/
|
||||
public class InfinityStorageManager extends SavedData {
|
||||
|
||||
/**
|
||||
* SavedData 文件名常量
|
||||
*/
|
||||
public static final String FILE_NAME = "eap_infinity_biginteger_cells";
|
||||
/**
|
||||
* 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充)
|
||||
*/
|
||||
public static InfinityStorageManager INSTANCE = null;
|
||||
/**
|
||||
* UUID -> 数据 的内存映射
|
||||
*/
|
||||
private final Map<UUID, InfinityDataStorage> cells = new HashMap<>();
|
||||
// 存储所有磁盘的Map,键为UUID,值为DataStorage对象
|
||||
private final Map<UUID, InfinityDataStorage> cells;
|
||||
|
||||
|
||||
// 构造方法,初始化磁盘Map
|
||||
public InfinityStorageManager() {
|
||||
setDirty();
|
||||
cells = new HashMap<>();
|
||||
// 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 NBT 构造:用于在世界加载时从存档恢复数据
|
||||
*/
|
||||
public InfinityStorageManager(CompoundTag nbt) {
|
||||
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
|
||||
for (int i = 0; i < cellList.size(); i++) {
|
||||
CompoundTag cell = cellList.getCompound(i);
|
||||
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data")));
|
||||
}
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
|
||||
*/
|
||||
public static InfinityStorageManager getForLevel(ServerLevel level) {
|
||||
if (INSTANCE == null && level != null) {
|
||||
INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME);
|
||||
}
|
||||
return INSTANCE;
|
||||
// 私有构造方法,用于从已有Map创建StorageManager
|
||||
private InfinityStorageManager(Map<UUID, InfinityDataStorage> cells) {
|
||||
// 确保使用已加载的数据
|
||||
this.cells = cells;
|
||||
// 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -66,49 +41,96 @@ public class InfinityStorageManager extends SavedData {
|
|||
ListTag cellList = new ListTag();
|
||||
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
|
||||
CompoundTag cell = new CompoundTag();
|
||||
cell.putUUID("uuid", entry.getKey());
|
||||
cell.put("data", entry.getValue().serializeNBT());
|
||||
cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey());
|
||||
cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT());
|
||||
cellList.add(cell);
|
||||
}
|
||||
nbt.put("list", cellList);
|
||||
nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList);
|
||||
// 写入当前格式版本号,便于未来迁移与兼容判断
|
||||
nbt.putInt(InfinityConstants.FORMAT_VERSION_FIELD, InfinityConstants.FORMAT_VERSION);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
|
||||
*/
|
||||
// 静态方法,从 NBT 数据反序列化创建 StorageManager 实例
|
||||
public static InfinityStorageManager readNbt(CompoundTag nbt) {
|
||||
// 读取格式版本,缺省视为 1(兼容旧档)
|
||||
int version = nbt.contains(InfinityConstants.FORMAT_VERSION_FIELD) ?
|
||||
nbt.getInt(InfinityConstants.FORMAT_VERSION_FIELD) :
|
||||
1;
|
||||
|
||||
Map<UUID, InfinityDataStorage> cells = new HashMap<>();
|
||||
// 从 NBT 中获取磁盘数据列表,指定类型为 CompoundTag(TAG_COMPOUND)
|
||||
ListTag cellList = nbt.getList(InfinityConstants.INFINITY_CELL_LIST, CompoundTag.TAG_COMPOUND);
|
||||
// 遍历 cellList 中的每个 CompoundTag
|
||||
for (int i = 0; i < cellList.size(); i++) {
|
||||
// 获取当前索引的 CompoundTag,表示单个磁盘的数据
|
||||
CompoundTag cell = cellList.getCompound(i);
|
||||
// 从 CompoundTag 中读取 UUID 和 DataStorage 数据,并存入 cells 映射
|
||||
cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA)));
|
||||
}
|
||||
// 使用加载的 cells 数据创建新的 StorageManager 实例
|
||||
return new InfinityStorageManager(cells);
|
||||
}
|
||||
|
||||
// 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途
|
||||
public Set<UUID> getAllLoadedUUIDs() {
|
||||
return Collections.unmodifiableSet(cells.keySet());
|
||||
}
|
||||
|
||||
|
||||
// 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
|
||||
public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
|
||||
cells.put(uuid, infinityDataStorage);
|
||||
// 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建某个 UUID 对应的数据容器
|
||||
*/
|
||||
// 删除某个 UUID 的持久化记录并标记为脏
|
||||
public void removeCell(UUID uuid) {
|
||||
cells.remove(uuid);
|
||||
// 标记数据为“脏”,确保移除操作会在下次保存时反映到磁盘
|
||||
setDirty();
|
||||
}
|
||||
|
||||
// 检查指定 UUID 是否存在于 disks 映射中
|
||||
public boolean hasUUID(UUID uuid) {
|
||||
// 返回 cells 映射是否包含指定 UUID
|
||||
return cells.containsKey(uuid);
|
||||
}
|
||||
|
||||
// 获取或创建某个 UUID 对应的数据容器
|
||||
public InfinityDataStorage getOrCreateCell(UUID uuid) {
|
||||
// 检查 cells 映射中是否不存在指定 UUID
|
||||
if (!cells.containsKey(uuid)) {
|
||||
updateCell(uuid, new InfinityDataStorage());
|
||||
}
|
||||
// 返回指定 UUID 对应的 DataStorage 对象
|
||||
return cells.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表)
|
||||
*/
|
||||
public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) {
|
||||
InfinityDataStorage cellToModify = getOrCreateCell(cellID);
|
||||
if (stackKeys != null && stackAmounts != null) {
|
||||
cellToModify.keys = stackKeys;
|
||||
cellToModify.amounts = stackAmounts;
|
||||
// 修改指定 UUID 的磁盘数据,包括堆栈键、数量和总项目数
|
||||
public void modifyDisk(UUID uuid, ListTag keys, ListTag amounts, BigInteger itemCount) {
|
||||
// 获取或创建指定 UUID 的 DataStorage 对象
|
||||
InfinityDataStorage cellToModify = getOrCreateCell(uuid);
|
||||
if (keys != null && amounts != null) {
|
||||
cellToModify.keys = keys;
|
||||
cellToModify.amounts = amounts;
|
||||
}
|
||||
updateCell(cellID, cellToModify);
|
||||
// 更新 DataStorage 的 itemCount 字段
|
||||
cellToModify.itemCount = itemCount;
|
||||
// 将修改后的 DataStorage 对象更新到 cells 映射
|
||||
updateCell(uuid, cellToModify);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除某个 UUID 的持久化记录并标记为脏
|
||||
*/
|
||||
public void removeCell(UUID uuid) {
|
||||
cells.remove(uuid);
|
||||
setDirty();
|
||||
// 静态方法,获取 StorageManager 的单例实例
|
||||
public static InfinityStorageManager getInstance(MinecraftServer server) {
|
||||
ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
|
||||
// 使用 DataStorage 的 computeIfAbsent 方法加载或创建 StorageManager 实例
|
||||
// 如果数据存在,则调用 readNbt 加载;否则调用默认构造器创建新实例
|
||||
return world.getDataStorage().computeIfAbsent(
|
||||
InfinityStorageManager::readNbt,
|
||||
InfinityStorageManager::new,
|
||||
InfinityConstants.SAVE_FILE_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.extendedae_plus.wireless.endpoint;
|
||||
|
||||
import appeng.api.networking.IGridNode;
|
||||
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 通用 IWirelessEndpoint:通过提供方块实体与节点的 Supplier 实现。
|
||||
*/
|
||||
public class GenericNodeEndpointImpl implements IWirelessEndpoint {
|
||||
private final Supplier<BlockEntity> blockEntitySupplier;
|
||||
private final Supplier<IGridNode> nodeSupplier;
|
||||
|
||||
public GenericNodeEndpointImpl(Supplier<BlockEntity> blockEntitySupplier, Supplier<IGridNode> nodeSupplier) {
|
||||
this.blockEntitySupplier = Objects.requireNonNull(blockEntitySupplier);
|
||||
this.nodeSupplier = Objects.requireNonNull(nodeSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getServerLevel() {
|
||||
var be = blockEntitySupplier.get();
|
||||
if (be == null) return null;
|
||||
Level lvl = be.getLevel();
|
||||
return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getBlockPos() {
|
||||
var be = blockEntitySupplier.get();
|
||||
return be != null ? be.getBlockPos() : BlockPos.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGridNode getGridNode() {
|
||||
return nodeSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEndpointRemoved() {
|
||||
var be = blockEntitySupplier.get();
|
||||
return be == null || be.isRemoved();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.extendedae_plus.wireless.endpoint;
|
||||
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.helpers.InterfaceLogicHost;
|
||||
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* IWirelessEndpoint 实现:基于 InterfaceLogicHost 与节点提供者。
|
||||
*/
|
||||
public class InterfaceNodeEndpointImpl implements IWirelessEndpoint {
|
||||
private final InterfaceLogicHost host;
|
||||
private final Supplier<IGridNode> nodeSupplier;
|
||||
|
||||
public InterfaceNodeEndpointImpl(InterfaceLogicHost host, Supplier<IGridNode> nodeSupplier) {
|
||||
this.host = Objects.requireNonNull(host);
|
||||
this.nodeSupplier = Objects.requireNonNull(nodeSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getServerLevel() {
|
||||
var be = host.getBlockEntity();
|
||||
if (be == null) return null;
|
||||
Level lvl = be.getLevel();
|
||||
return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getBlockPos() {
|
||||
var be = host.getBlockEntity();
|
||||
return be != null ? be.getBlockPos() : BlockPos.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGridNode getGridNode() {
|
||||
return nodeSupplier.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEndpointRemoved() {
|
||||
var be = host.getBlockEntity();
|
||||
return be == null || be.isRemoved();
|
||||
}
|
||||
}
|
||||
|
|
@ -50,8 +50,7 @@
|
|||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
}
|
||||
,
|
||||
},
|
||||
"multiplier": {
|
||||
"position": {
|
||||
"top": 80,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"variants": {
|
||||
"": { "model": "extendedae_plus:block/assembler_matrix_upload_core" }
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
"block.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
|
||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
|
||||
"block.extendedae_plus.network_pattern_controller": "Pattern Supplier State Controller",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core",
|
||||
|
||||
"extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix",
|
||||
"extendedae_plus.upload_to_matrix.success": "Pattern uploaded to the assembly matrix",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "Power ratio: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.speed": "Current speed multiplier: %d",
|
||||
"screen.extendedae_plus.entity_speed_ticker.multiplier": "Extra consumption multiplier: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§lInsufficient network energy",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "Requires Entity Acceleration Card(s) to enable acceleration",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.max": "Maximum up to 1024x speed",
|
||||
|
|
@ -85,5 +87,12 @@
|
|||
"config.extendedae_plus.option.entityTickerCost": "Entity Ticker Base Energy Cost",
|
||||
"config.extendedae_plus.option.entityTickerBlackList": "Entity Ticker Blacklist",
|
||||
"config.extendedae_plus.option.entityTickerMultipliers": "Entity Ticker Extra Consumption Multipliers",
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold"
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core",
|
||||
|
||||
"item.extendedae_plus.channel_card": "Channel Card",
|
||||
"item.extendedae_plus.channel_card.channel": "Frequency: %s",
|
||||
"item.extendedae_plus.channel_card.channel.unset": "Frequency: Unset",
|
||||
"item.extendedae_plus.channel_card.set": "Frequency set to: %s",
|
||||
"group.storage.name": "StorageBus"
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
"block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
"block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core": "装配矩阵上传核心",
|
||||
|
||||
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
|
||||
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "功耗比例: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.speed": "当前加速倍率: %d",
|
||||
"screen.extendedae_plus.entity_speed_ticker.multiplier": "额外消耗倍率: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§l网络能量不足",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
|
||||
|
|
@ -85,5 +87,19 @@
|
|||
"config.extendedae_plus.option.entityTickerCost": "实体加速器能量消耗基础值",
|
||||
"config.extendedae_plus.option.entityTickerBlackList": "实体加速器黑名单",
|
||||
"config.extendedae_plus.option.entityTickerMultipliers": "实体加速器额外消耗倍率",
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值"
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip": "装配矩阵上传核心",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload": "上传到装配矩阵",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_success": "样板已上传到装配矩阵",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "未在当前网络中找到已成型的装配矩阵",
|
||||
"block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "装配矩阵的样板仓已满或无法插入",
|
||||
|
||||
"item.extendedae_plus.channel_card": "频道卡",
|
||||
"item.extendedae_plus.channel_card.channel": "频率:%s",
|
||||
"item.extendedae_plus.channel_card.channel.unset": "频率:未设置",
|
||||
"item.extendedae_plus.channel_card.set": "已设置频率:%s",
|
||||
"group.pattern_provider.name": "样板供应器",
|
||||
"group.entity_ticker.name": "实体加速器",
|
||||
"group.storage.name": "存储总线"
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "minecraft:block/cube_all",
|
||||
"textures": {
|
||||
"all": "extendedae_plus:block/assembler_matrix_upload_core"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "extendedae_plus:block/assembler_matrix_upload_core"
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "extendedae_plus:item/channel_card"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 225 B |
Binary file not shown.
|
After Width: | Height: | Size: 455 B |
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"ingredients": [
|
||||
{ "item": "expatternprovider:assembler_matrix_wall" },
|
||||
{ "item": "minecraft:lever" }
|
||||
],
|
||||
"result": {
|
||||
"item": "extendedae_plus:assembler_matrix_upload_core",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"ingredients": [
|
||||
{ "item": "ae2:advanced_card" },
|
||||
{ "item": "extendedae_plus:wireless_transceiver" }
|
||||
],
|
||||
"result": { "item": "extendedae_plus:channel_card", "count": 1 }
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
"ae2.client.gui.PatternEncodingTermScreenMixin",
|
||||
"ae2.client.gui.PatternProviderCloseMixin",
|
||||
"ae2.client.gui.PatternProviderScreenMixin",
|
||||
"ae2.compat.PatternProviderScreenCompatMixin",
|
||||
"ae2.client.gui.ScreenCloseMixin",
|
||||
"ae2.client.gui.SlotGridLayoutMixin",
|
||||
"ae2.menu.CraftConfirmMenuGoBackMixin",
|
||||
|
|
@ -68,6 +69,16 @@
|
|||
"ae2.menu.PatternEncodingTermMenuMixin",
|
||||
"ae2.menu.PatternProviderMenuAdvancedMixin",
|
||||
"ae2.menu.PatternProviderMenuDoublingMixin",
|
||||
"ae2.compat.PatternProviderLogicCompatMixin",
|
||||
"ae2.compat.PatternProviderLogicHostCompatMixin",
|
||||
"ae2.compat.PatternProviderCompatMixin",
|
||||
"appflux.AppfluxPatternProviderLogicMixin",
|
||||
"ae2.helpers.patternprovider.PatternProviderLogicTickerMixin",
|
||||
"ae2.parts.AEBasePartClientSyncMixin",
|
||||
"ae2.parts.automation.IOBusPartChannelCardMixin",
|
||||
"ae2.parts.automation.IOBusPartTickerChannelCardMixin",
|
||||
"ae2.parts.storagebus.StorageBusPartChannelCardMixin",
|
||||
"ae2.parts.storagebus.StorageBusPartTickerChannelCardMixin",
|
||||
"ae2WTlib.ContainerUWirelessExPatternTerminalMixin",
|
||||
"extendedae.common.PartExPatternProviderMixin",
|
||||
"extendedae.common.TileExPatternProviderMixin",
|
||||
|
|
@ -75,9 +86,14 @@
|
|||
"extendedae.container.ContainerExPatternTerminalMixin",
|
||||
"extendedae.container.ContainerWirelessExPatternTerminalMixin",
|
||||
"gtceu.ModularUIContainerCloseMixin"
|
||||
"extendedae.container.ContainerWirelessExPatternTerminalMixin",
|
||||
"ae2.helpers.InterfaceLogicChannelCardMixin",
|
||||
"ae2.helpers.InterfaceLogicTickerMixin",
|
||||
"ae2.InterfaceLogicUpgradesMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"plugin": "com.extendedae_plus.mixin.MixinConditions",
|
||||
"priority": 1000
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user