From 1ee535391fd68d4684491e94d451b2fce40f346b Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 19:53:52 +0800
Subject: [PATCH 01/43] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=E6=97=A0?=
=?UTF-8?q?=E7=BA=BF=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/extendedae_plus/ExtendedAEPlus.java | 20 +-
.../InfinityBigIntegerCellHandler.java | 36 --
.../InfinityBigIntegerCellInventory.java | 435 ------------------
.../ae/items/InfinityBigIntegerCellItem.java | 81 ----
.../extendedae_plus/init/ModCreativeTabs.java | 2 +-
.../com/extendedae_plus/init/ModItems.java | 7 +-
.../util/storage/InfinityDataStorage.java | 61 ---
.../util/storage/InfinityStorageManager.java | 166 -------
8 files changed, 5 insertions(+), 803 deletions(-)
delete mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java
delete mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java
delete mode 100644 src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java
delete mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java
delete mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 5a6277c..6670527 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -1,18 +1,13 @@
package com.extendedae_plus;
-import appeng.api.storage.StorageCells;
import appeng.block.AEBaseEntityBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
-import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
-import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.*;
import com.extendedae_plus.network.ModNetwork;
-import com.extendedae_plus.util.storage.InfinityStorageManager;
import com.mojang.logging.LogUtils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
-import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
@@ -21,7 +16,6 @@ import net.neoforged.fml.common.Mod;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.common.NeoForge;
-import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
import org.slf4j.Logger;
@@ -56,10 +50,6 @@ public class ExtendedAEPlus {
// Note that this is necessary if and only if we want *this* class (ExtendedAEPlus) to respond directly to events.
// Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below.
NeoForge.EVENT_BUS.register(this);
- NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
-
- NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
- NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
// 注册配置:接入自定义的 ModConfigs
modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
@@ -75,7 +65,7 @@ public class ExtendedAEPlus {
LOGGER.info("HELLO FROM COMMON SETUP");
// 示例日志,避免引用不存在的模板 Config 字段
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
- StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
+
// 绑定 AE2 的 CraftingBlockEntity 到本模组的自定义加速器方块,避免 AEBaseEntityBlock.blockEntityType 为空
event.enqueueWork(() -> {
try {
@@ -120,13 +110,5 @@ public class ExtendedAEPlus {
// Do something when the server starts
LOGGER.info("HELLO from server starting");
}
-
-
- // 在世界加载时注册/加载 SavedData
- private static void onLevelLoad(LevelEvent.Load event) {
- if (event.getLevel() instanceof ServerLevel serverLevel) {
- InfinityStorageManager.getForLevel(serverLevel);
- }
- }
}
diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java
deleted file mode 100644
index bc7c29c..0000000
--- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.extendedae_plus.ae.api.storage;
-
-import appeng.api.storage.cells.ICellHandler;
-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);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java
deleted file mode 100644
index b7e3e14..0000000
--- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java
+++ /dev/null
@@ -1,435 +0,0 @@
-package com.extendedae_plus.ae.api.storage;
-
-import appeng.api.config.Actionable;
-import appeng.api.networking.security.IActionSource;
-import appeng.api.stacks.AEItemKey;
-import appeng.api.stacks.AEKey;
-import appeng.api.stacks.KeyCounter;
-import appeng.api.storage.cells.CellState;
-import appeng.api.storage.cells.ISaveProvider;
-import appeng.api.storage.cells.StorageCell;
-import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
-import com.extendedae_plus.util.storage.InfinityDataStorage;
-import com.extendedae_plus.util.storage.InfinityStorageManager;
-import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import net.minecraft.core.HolderLookup;
-import net.minecraft.core.component.DataComponents;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.network.chat.Component;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.component.CustomData;
-import net.neoforged.neoforge.event.server.ServerStoppingEvent;
-import net.neoforged.neoforge.event.tick.ServerTickEvent;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.math.RoundingMode;
-import java.text.DecimalFormat;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * InfinityBigIntegerCellInventory
- *
- * 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。
- * 主要职责:
- * - 在内存中维护条目映射 (AEKey -> BigInteger 数量)
- * - 提供插入/提取/列举/持久化等操作的实现
- * - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化
- *
- * 重要字段:
- * - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息
- * - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化
- * - storedMap: 延迟初始化的内存映射,减少未使用时内存占用
- * - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描
- * - isPersisted: 标记内存状态是否已同步到持久层
- */
-public class InfinityBigIntegerCellInventory implements StorageCell {
-
- // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化)
- private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>();
- // 数字格式化对象,保留两位小数(复用以减少对象分配)
- private static final DecimalFormat DF = new DecimalFormat("#.##");
-
- // 关联的 ItemStack(含可能的 uuid NBT)
- private final ItemStack stack;
- // AE2 提供的保存提供者,用于在容器中批量保存时触发回调
- private final ISaveProvider container;
- // 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化)
- private Object2ObjectMap storedMap = null;
- // 标记是否已持久化到 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(ServerTickEvent.Post event) {
- // NeoForge 提供 Pre/Post 子类以区分阶段,Post 对应原来的 Phase.END
- InfinityBigIntegerCellInventory inv;
- // 处理本次 tick 中的全部待持久化项
- while ((inv = PENDING_PERSIST.poll()) != null) {
- try {
- if (!inv.isPersisted) {
- inv.persist();
- }
- } catch (Throwable ignored) {
- // 忽略单项错误,继续处理其余队列
- }
- }
- }
-
- // 在服务器停止时被调用,立即强制持久化队列中的所有实例
- public static void onServerStopping(ServerStoppingEvent event) {
- InfinityBigIntegerCellInventory inv;
- while ((inv = PENDING_PERSIST.poll()) != null) {
- try {
- if (!inv.isPersisted) {
- inv.persist();
- }
- } catch (Throwable ignored) {
- // 忽略单项错误,继续尝试持久化其它实例
- }
- }
- // 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发)
- try {
- var stor = getStorageInstance();
- if (stor != null) stor.setDirty();
- } catch (Throwable ignored) {
- }
- }
-
- // 将 BigInteger 格式化为带单位的字符串,保留两位小数
- public static String formatBigInteger(BigInteger number) {
- // 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行
- BigDecimal bd = new BigDecimal(number);
- BigDecimal thousand = new BigDecimal(1000);
- String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
- int idx = 0;
- while (bd.compareTo(thousand) >= 0 && idx < units.length - 1) {
- bd = bd.divide(thousand, 2, RoundingMode.HALF_UP);
- idx++;
- }
- if (idx == 0) {
- return bd.setScale(0, RoundingMode.DOWN).toPlainString();
- }
- return DF.format(bd.doubleValue()) + units[idx];
- }
-
- // 获取当前存储单元的数据存储对象
- private InfinityDataStorage getCellStorage() {
- if (this.getUUID() == null) {
- // 如果没有UUID,返回空存储
- return InfinityDataStorage.EMPTY;
- } else {
- // 否则获取或创建对应UUID的存储
- return getStorageInstance().getOrCreateCell(getUUID());
- }
- }
-
- // 获取存储单元状态(空/非空)
- @Override
- public CellState getStatus() {
- if (this.getCellStoredMap().isEmpty()) {
- return CellState.EMPTY;
- }
- return CellState.NOT_EMPTY;
- }
-
- // 获取存储单元的待机能耗
- @Override
- public double getIdleDrain() {
- return 512;
- }
-
- // 获取存储单元的描述(此处返回null,可自定义)
- @Override
- public Component getDescription() {
- return null;
- }
-
- // 判断物品堆栈是否有UUID
- public boolean hasUUID() {
- CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
- if (customData == null) return false;
- CompoundTag tag = customData.copyTag();
- return tag != null && tag.contains("uuid");
- }
-
- // 获取物品堆栈的UUID
- public UUID getUUID() {
- CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
- if (customData == null) return null;
- CompoundTag tag = customData.copyTag();
- if (tag != null && tag.contains("uuid")) {
- return tag.getUUID("uuid");
- }
- return null;
- }
-
- // 获取或初始化存储映射
- private Object2ObjectMap getCellStoredMap() {
- if (storedMap == null) {
- storedMap = new Object2ObjectOpenHashMap<>();
- this.loadCellStoredMap();
- }
- return storedMap;
- }
-
- // 从存储中加载物品映射
- private void loadCellStoredMap() {
- boolean corruptedTag = false; // 标记数据是否损坏
- if (!this.hasUUID()) return;
-
- ListTag keys = this.getCellStorage().keys;
- ListTag amounts = this.getCellStorage().amounts;
-
- int len = Math.min(keys.size(), amounts.size());
-
- HolderLookup.Provider registries = getStorageInstance().getRegistries();
-
- for (int i = 0; i < len; i++) {
- AEKey key = AEKey.fromTagGeneric(registries,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);
- }
- }
- }
-
- // 获取所有可用的物品堆栈及其数量
- @Override
- public void getAvailableStacks(KeyCounter out) {
- BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
- Object2ObjectMap map = getCellStoredMap();
- for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) {
- AEKey key = entry.getKey();
- BigInteger value = entry.getValue();
-
- // 当前 KeyCounter 中已有的值(long)
- long existing = out.get(key);
-
- // 将 existing 与当前 value 做 BigInteger 累加并饱和到 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)
- if (existing == Long.MAX_VALUE) {
- // 已经饱和,无需再添加
- continue;
- }
- long delta;
- if (toSet == Long.MAX_VALUE) {
- delta = Long.MAX_VALUE - existing;
- } else {
- delta = toSet - existing;
- }
- if (delta != 0) {
- out.add(key, delta);
- }
- }
- }
-
- // 持久化存储单元数据到全局存储
- @Override
- public void persist() {
- if (this.isPersisted)
- return;
- Object2ObjectMap map = this.getCellStoredMap();
- if (map.isEmpty()) {
- // 如果存储为空,移除UUID和全局存储中的数据
- if (this.hasUUID()) {
- getStorageInstance().removeCell(getUUID());
- // 从 CustomData 中移除缓存字段
- try {
- CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
- tag.remove("uuid");
- tag.remove("total");
- });
- } catch (Throwable ignored) {
- }
- }
- return;
- }
- // 构建要保存的Key和数量列表(混合表示:long 或 string)
- ListTag amountTags = new ListTag();
- ListTag keys = new ListTag();
- for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) {
- BigInteger amount = entry.getValue();
- if (amount.compareTo(BigInteger.ZERO) > 0) {
- keys.add(entry.getKey().toTagGeneric(getStorageInstance().getRegistries()));
- 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 的 CustomData(优先使用 long)
- try {
- CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
- if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
- tag.putLong("total", totalStored.longValue());
- } else {
- tag.putString("total", totalStored.toString());
- }
- // 将当前已存储的不同物品种类数缓存到 CustomData(键名: "types"),用于客户端 tooltip 显示
- int typesCount = this.getCellStoredMap().size();
- tag.putInt("types", typesCount);
- });
- } catch (Throwable ignored) {
- }
- isPersisted = true;
- }
-
- // 插入物品到存储单元
- @Override
- public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
- // 数量为0或类型不匹配直接返回
- if (amount == 0)
- return 0;
- // 不允许存储无限单元自身
- if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
- return 0;
- // 如果没有UUID,生成UUID并初始化存储
- if (!this.hasUUID()) {
- UUID id = UUID.randomUUID();
- try {
- CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID("uuid", id));
- } catch (Throwable ignored) {
- }
- getStorageInstance().getOrCreateCell(getUUID());
- // 确保 storedMap 初始化并从持久层加载数据
- this.getCellStoredMap();
- }
- Object2ObjectMap map = this.getCellStoredMap();
- BigInteger currentAmount = map.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));
- this.saveChanges();
- }
- return amount;
- }
-
- // 从存储单元提取物品
- @Override
- public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
- Object2ObjectMap map = this.getCellStoredMap();
- BigInteger currentAmount = map.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 (mode == Actionable.MODULATE) {
- map.remove(what);
- // 更新 cached total
- // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数
- totalStored = totalStored.subtract(currentAmount);
- this.saveChanges();
- }
- return ret;
- } else {
- // 提取部分
- if (mode == Actionable.MODULATE) {
- map.put(what, currentAmount.subtract(requested));
- // 更新 cached total
- totalStored = totalStored.subtract(requested);
- this.saveChanges();
- }
- return amount;
- }
- }
- return 0;
- }
-
- // 获取存储单元内所有物品的总数量(格式化字符串)
- public String getTotalStorage() {
- // 使用缓存的 totalStored,避免每次全表扫描
- return formatBigInteger(totalStored);
- }
-}
diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java
deleted file mode 100644
index 7002ba9..0000000
--- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.extendedae_plus.ae.items;
-
-import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
-import com.google.common.base.Preconditions;
-import net.minecraft.ChatFormatting;
-import net.minecraft.core.component.DataComponents;
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.LongTag;
-import net.minecraft.nbt.Tag;
-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.item.component.CustomData;
-import org.jetbrains.annotations.NotNull;
-
-import java.math.BigInteger;
-import java.util.List;
-
-public class InfinityBigIntegerCellItem extends Item {
-
- public InfinityBigIntegerCellItem() {
- super(new Properties().stacksTo(1).fireResistant());
- }
-
- @Override
- public void appendHoverText(ItemStack stack,
- @NotNull TooltipContext context,
- List tooltip,
- @NotNull TooltipFlag tooltipFlag) {
- tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
- tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
-
- Preconditions.checkArgument(stack.getItem() == this);
- CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
- if (customData != null) {
- CompoundTag tag = customData.copyTag();
-
- if (tag != null && tag.contains("uuid")) {
- String uuidStr = tag.getUUID("uuid").toString();
- tooltip.add(
- Component.literal("UUID: ")
- .withStyle(ChatFormatting.GRAY)
- .append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
- );
-
- if (tag.contains("types")) {
- try {
- int types = tag.getInt("types");
- tooltip.add(
- Component.literal("Types: ")
- .withStyle(ChatFormatting.GRAY)
- .append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
- );
- } catch (Exception ignored) {
- }
- }
-
- if (tag.contains("total")) {
- BigInteger total = BigInteger.ZERO;
- Tag t = tag.get("total");
- try {
- if (t instanceof LongTag) {
- total = BigInteger.valueOf(tag.getLong("total"));
- } else {
- String s = tag.getString("total");
- total = new BigInteger(s);
- }
- } catch (Exception ignored) {
- }
- String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
- tooltip.add(
- Component.literal("Byte: ")
- .withStyle(ChatFormatting.GRAY)
- .append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
- );
- }
- }
- }
- }
-}
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index 2846159..f07a83b 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -27,7 +27,7 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
- output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
+// output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
})
.build());
}
diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java
index 113ec98..71a6220 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -1,7 +1,6 @@
package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
-import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.neoforged.neoforge.registries.DeferredItem;
@@ -48,8 +47,8 @@ public final class ModItems {
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
);
- public static final DeferredItem- INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
- "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
- );
+// public static final DeferredItem
- INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
+// "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
+// );
}
diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java
deleted file mode 100644
index 4ef9036..0000000
--- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.extendedae_plus.util.storage;
-
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.ListTag;
-import net.minecraft.nbt.Tag;
-
-/**
- * InfinityDataStorage
- *
- * 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。
- * 数据结构说明:
- * - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目
- * - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示:
- * - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值
- * - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式
- *
- * 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。
- */
-public class InfinityDataStorage {
-
- /** 空实例(表示没有数据) */
- 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;
-
- public InfinityDataStorage() {
- this(new ListTag(), new ListTag());
- }
-
- private InfinityDataStorage(ListTag keys, ListTag amounts) {
- this.keys = keys;
- this.amounts = amounts;
- }
-
- /**
- * 将当前数据封装为 CompoundTag 以写入存档
- */
- public CompoundTag serializeNBT() {
- CompoundTag nbt = new CompoundTag();
- nbt.put("keys", keys);
- nbt.put("amounts", amounts);
- return nbt;
- }
-
- /**
- * 从存档读取数据并构造实例
- */
- 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);
- }
-}
diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java
deleted file mode 100644
index ea48942..0000000
--- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package com.extendedae_plus.util.storage;
-
-import net.minecraft.core.HolderLookup;
-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 org.jetbrains.annotations.Nullable;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * InfinityStorageManager
- *
- * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
- * 功能要点:
- * - 在世界加载时从存档恢复所有 cell 的数据
- * - 提供按 UUID 获取/创建单个 cell 的数据容器
- * - 在世界保存时将内存数据打包为 NBT 写回存档
- */
-public class InfinityStorageManager extends SavedData {
- private static final Factory FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt);
-
- /**
- * SavedData 文件名常量
- */
- public static final String FILE_NAME = "eap_infinity_biginteger_cells";
- /**
- * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充)
- */
- public static InfinityStorageManager INSTANCE = null;
- /**
- * UUID -> 数据 的内存映射
- */
- private final Map cells;
-
- @Nullable
- private WeakReference registries;
-
- public InfinityStorageManager() {
- this.cells = new HashMap<>();
- setDirty();
- }
-
- public InfinityStorageManager(Map cells) {
- this.cells = cells;
- setDirty();
- }
-
- /**
- * 从 NBT 构造:用于在世界加载时从存档恢复数据
- */
- public InfinityStorageManager(CompoundTag nbt) {
- this.cells = new HashMap<>();
- 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(FACTORY, FILE_NAME);
- INSTANCE.registries = new WeakReference<>(level.registryAccess());
- }
- return INSTANCE;
- }
-
- public HolderLookup.Provider getRegistries() {
- var r = this.registries;
- if (r == null) {
- throw new IllegalStateException("StorageManager was not initialized properly.");
- }
-
- var registries = r.get();
- if (registries == null) {
- throw new IllegalStateException("Using a StorageManager whose server was already closed");
- }
-
- return registries;
- }
-
- @Override
- public @NotNull CompoundTag save(@NotNull CompoundTag nbt, HolderLookup.@NotNull Provider registries) {
- // 将内存中的所有 cell 序列化为一个 ListTag
- ListTag cellList = new ListTag();
- for (Map.Entry entry : cells.entrySet()) {
- CompoundTag cell = new CompoundTag();
- cell.putUUID("uuid", entry.getKey());
- cell.put("data", entry.getValue().serializeNBT());
- cellList.add(cell);
- }
- nbt.put("list", cellList);
- return nbt;
- }
-
- /**
- * 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
- */
- public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
- cells.put(uuid, infinityDataStorage);
- setDirty();
- }
-
- /**
- * 获取或创建某个 UUID 对应的数据容器
- */
- public InfinityDataStorage getOrCreateCell(UUID uuid) {
- if (!cells.containsKey(uuid)) {
- updateCell(uuid, new InfinityDataStorage());
- }
- 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;
- }
- updateCell(cellID, cellToModify);
- }
-
- /**
- * 删除某个 UUID 的持久化记录并标记为脏
- */
- public void removeCell(UUID uuid) {
- cells.remove(uuid);
- setDirty();
- }
-
- public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) {
- Map cell = new HashMap<>();
- ListTag diskList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
- for (int i = 0; i < diskList.size(); i++) {
- CompoundTag disk = diskList.getCompound(i);
- cell.put(disk.getUUID("uuid"), InfinityDataStorage.loadFromNBT(disk.getCompound("data")));
- }
- return new InfinityStorageManager(cell);
- }
-
- public static InfinityStorageManager getInstance(MinecraftServer server) {
- ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
- InfinityStorageManager manager = null;
- if (world != null) {
- manager = world.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME);
- }
- if (manager != null) {
- manager.registries = new WeakReference<>(server.registryAccess());
- }
- return manager;
- }
-}
From 51c5d4e3d7b52d5e6fa1a7ae26603896b0961fd7 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 20:20:41 +0800
Subject: [PATCH 02/43] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=85=8D?=
=?UTF-8?q?=E7=BD=AEAE=E5=90=88=E6=88=90=E6=9A=82=E5=81=9C=E6=A3=80?=
=?UTF-8?q?=E6=9F=A5=E9=98=88=E5=80=BC=E4=BB=A5=E9=99=8D=E4=BD=8E=E4=B8=8A?=
=?UTF-8?q?=E4=B8=8B=E6=96=87=E5=88=87=E6=8D=A2=E9=A2=91=E7=8E=87=EF=BC=8C?=
=?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=20100000?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../extendedae_plus/config/ModConfigs.java | 10 ++++++++-
.../mixin/ae2/CraftingCalculationMixin.java | 21 +++++++++++++++++++
.../resources/extendedae_plus.mixins.json | 5 +++--
3 files changed, 33 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/CraftingCalculationMixin.java
diff --git a/src/main/java/com/extendedae_plus/config/ModConfigs.java b/src/main/java/com/extendedae_plus/config/ModConfigs.java
index 7a3ae32..d74f914 100644
--- a/src/main/java/com/extendedae_plus/config/ModConfigs.java
+++ b/src/main/java/com/extendedae_plus/config/ModConfigs.java
@@ -11,6 +11,7 @@ public final class ModConfigs {
public static final ModConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
+ public static final ModConfigSpec.IntValue CRAFTING_PAUSE_THRESHOLD;
static {
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
@@ -41,6 +42,12 @@ public final class ModConfigs {
)
.define("patternTerminalShowSlotsDefault", true);
+ CRAFTING_PAUSE_THRESHOLD = builder
+ .comment(
+ "值越大将减少AE构建合成计划过程中的 wait/notify 次数,提升吞吐但会降低调度响应性"
+ )
+ .defineInRange("craftingPauseThreshold", 100000, 100, Integer.MAX_VALUE);
+
// end general
builder.pop();
@@ -88,5 +95,6 @@ public final class ModConfigs {
COMMON_SPEC = builder.build();
}
- private ModConfigs() {}
+ private ModConfigs() {
+ }
}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCalculationMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCalculationMixin.java
new file mode 100644
index 0000000..255abd3
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCalculationMixin.java
@@ -0,0 +1,21 @@
+package com.extendedae_plus.mixin.ae2;
+
+import appeng.crafting.CraftingCalculation;
+import com.extendedae_plus.config.ModConfigs;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.Constant;
+import org.spongepowered.asm.mixin.injection.ModifyConstant;
+
+/**
+ * Mixin 用于增加 CraftingCalculation.handlePausing() 中 incTime 的阈值。
+ * 调整 CRAFTING_PAUSE_THRESHOLD 可以控制计算让出控制权的频率。
+ */
+@Mixin(value = CraftingCalculation.class, remap = false)
+public abstract class CraftingCalculationMixin {
+ // 可调节的阈值:在执行 pause 检查前 handlePausing 被调用的次数。
+ // 增大此值可以减少 wait/notify 的频率(提升吞吐量但降低响应速度)。
+ @ModifyConstant(method = "handlePausing", constant = @Constant(intValue = 100))
+ private int modifyIncTimeThreshold(int original) {
+ return ModConfigs.CRAFTING_PAUSE_THRESHOLD.get();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json
index 3f9f3e1..314e662 100644
--- a/src/main/resources/extendedae_plus.mixins.json
+++ b/src/main/resources/extendedae_plus.mixins.json
@@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_21",
"mixins": [
"ae2.AEProcessingPatternMixin",
+ "ae2.CraftingCalculationMixin",
"ae2.CraftingCPUClusterMixin",
"ae2.EncodedPatternItemMixin",
"ae2.QuartzCuttingKnifeItemMixin",
@@ -36,18 +37,18 @@
"PickFromWirelessMixin",
"accessor.AbstractContainerScreenAccessor",
"accessor.ScreenAccessor",
- "ae2.menu.CraftConfirmMenuGoBackMixin",
"ae2.accessor.AEBaseScreenAccessor",
"ae2.accessor.AEBaseScreenInvoker",
"ae2.accessor.MEStorageScreenAccessor",
"ae2.accessor.PatternAccessTermScreenAccessor",
"ae2.accessor.PatternAccessTermScreenSlotsRowAccessor",
- "ae2.client.gui.InterfaceScreenMixin",
"ae2.client.gui.AEBaseScreenMixin",
+ "ae2.client.gui.InterfaceScreenMixin",
"ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.PatternProviderCloseMixin",
"ae2.client.gui.PatternProviderScreenMixin",
"ae2.client.gui.SlotGridLayoutMixin",
+ "ae2.menu.CraftConfirmMenuGoBackMixin",
"extendedae.accessor.GuiExPatternTerminalAccessor",
"extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor",
"extendedae.client.HighlightButtonMixin",
From 4e2be178717ce10e9a2072349b70ec01b014366d Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 20:28:11 +0800
Subject: [PATCH 03/43] =?UTF-8?q?feat:=20=E6=94=BE=E5=AE=BD=E6=89=A9?=
=?UTF-8?q?=E5=B1=95=E4=BE=9B=E5=BA=94=E5=99=A8=E5=86=85=E7=9A=84=E6=A0=B7?=
=?UTF-8?q?=E6=9D=BF=E6=89=8B=E5=8A=A8=E5=80=8D=E5=A2=9E=E9=99=90=E5=88=B6?=
=?UTF-8?q?(=E4=B8=8D=E8=B6=85int),=20=E8=B0=83=E6=95=B4=E6=A0=B7=E6=9D=BF?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E4=BB=A5=E6=94=AF=E6=8C=81=E5=A4=A7?=
=?UTF-8?q?=E6=95=B0=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ContainerExPatternProviderMixin.java | 27 +++++++++----
.../util/NumberFormatUtil.java | 39 +++++++++++++------
2 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
index a91cb09..145bc62 100644
--- a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
@@ -13,7 +13,6 @@ import com.glodblock.github.extendedae.container.ContainerExPatternProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
-import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.Unique;
@@ -21,6 +20,7 @@ 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;
@Pseudo
@@ -107,7 +107,7 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
}
@Unique
- private boolean eap$checkModify(java.util.List stacks, int scale, boolean div) {
+ private boolean eap$checkModify(List stacks, int scale, boolean div) {
if (stacks == null) return false;
if (div) {
for (var stack : stacks) {
@@ -121,10 +121,12 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
} else {
for (var stack : stacks) {
if (stack != null) {
- long upper = 999999L * stack.what().getAmountPerUnit();
- if (stack.amount() * scale > upper) {
+ long amt = stack.amount();
+ // 先检查乘法是否会导致超出 Long.MAX_VALUE,避免溢出
+ if (amt > Integer.MAX_VALUE / scale) {
return false;
}
+ // 已移除原有的业务上限检查(999999 * amountPerUnit),仅保留溢出检查
}
}
return true;
@@ -132,12 +134,23 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
}
@Unique
- private java.util.List eap$modifyStacks(java.util.List src, int scale, boolean div) {
- var dst = new java.util.ArrayList(src.size());
+ private List eap$modifyStacks(List src, int scale, boolean div) {
+ var dst = new ArrayList(src.size());
for (var stack : src) {
if (stack != null) {
long amt = stack.amount();
- long newAmt = div ? (amt / scale) : (amt * scale);
+ long newAmt;
+ if (div) { // 如果是除法操作
+ newAmt = amt / scale; // 执行除法
+ } else { // 如果是乘法操作
+ // 防御性检查:确保不会发生溢出,尽管上层应已验证
+ if (amt > Integer.MAX_VALUE / scale) {
+ // 遇到潜在溢出时跳过,保持为 null;调用方应通过 eap$checkModify 避免此分支
+ dst.add(null);
+ continue;
+ }
+ newAmt = amt * scale; // 执行乘法
+ }
dst.add(new GenericStack(stack.what(), newAmt));
} else {
dst.add(null);
diff --git a/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java b/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java
index 760b7be..b9aec75 100644
--- a/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java
+++ b/src/main/java/com/extendedae_plus/util/NumberFormatUtil.java
@@ -1,5 +1,7 @@
package com.extendedae_plus.util;
+import java.text.DecimalFormat;
+
/**
* 数字格式化工具类,提供大数字和小数的格式化功能
*/
@@ -15,13 +17,18 @@ public class NumberFormatUtil {
public static String formatNumber(long number) {
if (number < 1000) {
return String.valueOf(number);
- } else if (number < 1000000) {
- double value = number / 1000.0;
- return formatDecimal(value, "k");
- } else {
- double value = number / 1000000.0;
- return formatDecimal(value, "m");
}
+
+ String[] preFixes = new String[]{"k", "M", "G", "T", "P", "E", "Z", "Y"};
+ double value = number;
+ String level = "";
+
+ for (int offset = 0; value >= 1000.0 && offset < preFixes.length; ++offset) {
+ value /= 1000.0;
+ level = preFixes[offset];
+ }
+
+ return formatDecimal(value, level);
}
/**
@@ -31,16 +38,24 @@ public class NumberFormatUtil {
*/
public static String formatNumberWithDecimal(double value) {
if (value < 1000) {
+ DecimalFormat smallDf = new DecimalFormat("#.##");
+ // 小于1000时,若是整数则显示整数,否则显示最多两位小数
if (value == (long) value) {
return String.valueOf((long) value);
} else {
- return String.format("%.1f", value).replaceAll("\\.0$", "");
+ return smallDf.format(value);
}
- } else if (value < 1000000) {
- return formatDecimal(value / 1000.0, "k");
- } else {
- return formatDecimal(value / 1000000.0, "m");
}
+
+ String[] preFixes = new String[]{"k", "M", "G", "T", "P", "E", "Z", "Y"};
+ String level = "";
+ for (int offset = 0; value >= 1000.0 && offset < preFixes.length; ++offset) {
+ value /= 1000.0;
+ level = preFixes[offset];
+ }
+
+ DecimalFormat df = new DecimalFormat("#.##");
+ return df.format(value) + level;
}
/**
@@ -54,7 +69,7 @@ public class NumberFormatUtil {
if (Math.abs(value - Math.round(value)) < 0.001) {
return String.valueOf(Math.round(value)) + suffix;
} else {
- // 修复重复后缀问题:先格式化数字,再添加后缀
+ // 使用一位小数显示,去掉末尾 .0
String formatted = String.format("%.1f", value).replaceAll("\\.0$", "");
return formatted + suffix;
}
From 20262814ba5c36bd948f676c4078346d8467f527 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 20:40:59 +0800
Subject: [PATCH 04/43] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E6=97=A0?=
=?UTF-8?q?=E7=BA=BF=E6=94=B6=E5=8F=91=E5=99=A8=E7=BB=A7=E6=89=BFAEBaseBlo?=
=?UTF-8?q?ckEntity=E4=BB=A5=E6=94=AF=E6=8C=81ae=E6=96=B9=E5=9D=97?=
=?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D=E5=8A=9F=E8=83=BD;=20=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0jade=E4=BB=8E=E8=8A=82=E7=82=B9=E6=96=B9=E5=9D=97?=
=?UTF-8?q?=E6=98=BE=E7=A4=BA=E4=B8=BB=E8=8A=82=E7=82=B9=E5=90=8D=E7=A7=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../wireless/WirelessTransceiverBlockEntity.java | 12 ++++++------
.../WirelessTransceiverJadePluginComponents.java | 7 ++++++-
.../jade/WirelessTransceiverProvider.java | 3 +++
3 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java
index 9ac4d75..b4dd71d 100644
--- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java
+++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java
@@ -2,6 +2,7 @@ package com.extendedae_plus.content.wireless;
import appeng.api.networking.*;
import appeng.api.util.AECableType;
+import appeng.blockentity.AEBaseBlockEntity;
import com.extendedae_plus.init.ModBlockEntities;
import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.wireless.IWirelessEndpoint;
@@ -13,7 +14,6 @@ import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
-import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
@@ -27,7 +27,7 @@ import java.util.Objects;
* - 集成 AE2 节点;
* - 集成无线主/从逻辑。
*/
-public class WirelessTransceiverBlockEntity extends BlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
+public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
private IManagedGridNode managedNode;
@@ -55,7 +55,7 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
}
@Override
- public appeng.api.util.AECableType getCableConnectionType(Direction dir) {
+ public AECableType getCableConnectionType(Direction dir) {
// 根据相邻方块的实际连接类型渲染(优先采用相邻主机返回的类型),回退为 GLASS。
if (this.level == null) return AECableType.GLASS;
var adjacentPos = this.worldPosition.relative(dir);
@@ -188,7 +188,7 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
/* ===================== NBT ===================== */
@Override
- protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
+ public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.saveAdditional(tag, registries);
tag.putLong("frequency", frequency);
tag.putBoolean("master", masterMode);
@@ -199,8 +199,8 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
}
@Override
- public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
- super.loadAdditional(tag, registries);
+ public void loadTag(CompoundTag tag, HolderLookup.Provider registries) {
+ super.loadTag(tag, registries);
this.frequency = tag.getLong("frequency");
this.masterMode = tag.getBoolean("master");
this.locked = tag.getBoolean("locked");
diff --git a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java
index e653a0b..22095a6 100644
--- a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java
+++ b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java
@@ -37,7 +37,12 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr
if (data.contains("masterMode") && !data.getBoolean("masterMode") && data.contains("masterPos")) {
BlockPos pos = BlockPos.of(data.getLong("masterPos"));
String dim = data.contains("masterDim") ? data.getString("masterDim") : "";
- tooltip.add(Component.literal("主节点位置: (" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
+ String customName = data.contains("customName") ? data.getString("customName") : null;
+ if (customName != null) {
+ tooltip.add(Component.literal("主节点: " + customName + "(" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
+ } else {
+ tooltip.add(Component.literal("主节点位置: (" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
+ }
if (!dim.isEmpty()) {
tooltip.add(Component.literal("维度: " + dim));
}
diff --git a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java
index cfec04b..acf11d3 100644
--- a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java
+++ b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java
@@ -46,6 +46,9 @@ public enum WirelessTransceiverProvider implements IServerDataProvider
Date: Sat, 20 Sep 2025 21:25:26 +0800
Subject: [PATCH 05/43] =?UTF-8?q?=E5=9B=9E=E9=80=80=E6=A0=B7=E6=9D=BF?=
=?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=99=A8=E6=8C=89=E9=92=AE=E5=80=8D=E5=A2=9E?=
=?UTF-8?q?=E7=9A=84=E8=B0=83=E6=95=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ContainerExPatternProviderMixin.java | 26 +++++--------------
1 file changed, 6 insertions(+), 20 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
index 145bc62..d4fa16c 100644
--- a/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/extendedae/container/ContainerExPatternProviderMixin.java
@@ -20,7 +20,6 @@ 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;
@Pseudo
@@ -107,7 +106,7 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
}
@Unique
- private boolean eap$checkModify(List stacks, int scale, boolean div) {
+ private boolean eap$checkModify(java.util.List stacks, int scale, boolean div) {
if (stacks == null) return false;
if (div) {
for (var stack : stacks) {
@@ -121,12 +120,10 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
} else {
for (var stack : stacks) {
if (stack != null) {
- long amt = stack.amount();
- // 先检查乘法是否会导致超出 Long.MAX_VALUE,避免溢出
- if (amt > Integer.MAX_VALUE / scale) {
+ long upper = 999999L * stack.what().getAmountPerUnit();
+ if (stack.amount() * scale > upper) {
return false;
}
- // 已移除原有的业务上限检查(999999 * amountPerUnit),仅保留溢出检查
}
}
return true;
@@ -134,23 +131,12 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
}
@Unique
- private List eap$modifyStacks(List src, int scale, boolean div) {
- var dst = new ArrayList(src.size());
+ private java.util.List eap$modifyStacks(java.util.List src, int scale, boolean div) {
+ var dst = new java.util.ArrayList(src.size());
for (var stack : src) {
if (stack != null) {
long amt = stack.amount();
- long newAmt;
- if (div) { // 如果是除法操作
- newAmt = amt / scale; // 执行除法
- } else { // 如果是乘法操作
- // 防御性检查:确保不会发生溢出,尽管上层应已验证
- if (amt > Integer.MAX_VALUE / scale) {
- // 遇到潜在溢出时跳过,保持为 null;调用方应通过 eap$checkModify 避免此分支
- dst.add(null);
- continue;
- }
- newAmt = amt * scale; // 执行乘法
- }
+ long newAmt = div ? (amt / scale) : (amt * scale);
dst.add(new GenericStack(stack.what(), newAmt));
} else {
dst.add(null);
From e45bcf87b04468716dba35259e348e32f80c6b20 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 22:05:01 +0800
Subject: [PATCH 06/43] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=99=BA?=
=?UTF-8?q?=E8=83=BD=E7=B3=BB=E5=88=97=E5=AF=B9=E9=AB=98=E7=BA=A7ae?=
=?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=99=A8=E7=9A=84=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 6 +
...ernProviderLogicContainsRedirectMixin.java | 38 +++++
...vPatternProviderLogicPatternsAccessor.java | 14 ++
...dvPatternProviderMenuAdvancedAccessor.java | 12 ++
.../gui/AdvPatternProviderScreenMixin.java | 139 ++++++++++++++++++
.../SmallAdvPatternProviderScreenMixin.java | 139 ++++++++++++++++++
.../AdvPatternProviderLogicAdvancedMixin.java | 94 ++++++++++++
.../AdvPatternProviderLogicDoublingMixin.java | 76 ++++++++++
.../AdvPatternProviderMenuAdvancedMixin.java | 43 ++++++
.../AdvPatternProviderMenuDoublingMixin.java | 41 ++++++
.../PatternProviderLogicAdvancedMixin.java | 9 +-
.../PatternProviderLogicDoublingMixin.java | 9 +-
.../ToggleAdvancedBlockingC2SPacket.java | 47 ++++--
.../network/ToggleSmartDoublingC2SPacket.java | 35 +++--
.../resources/extendedae_plus.mixins.json | 9 ++
15 files changed, 675 insertions(+), 36 deletions(-)
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java
diff --git a/build.gradle b/build.gradle
index 9687e17..8bbce35 100644
--- a/build.gradle
+++ b/build.gradle
@@ -162,6 +162,12 @@ dependencies {
// runtimeOnly "mekanism:Mekanism:1.21.1-10.7.0.55"
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
+ //aea
+ implementation "curse.maven:advancedae-1084104:6921281"
+
+ //geckolib
+ runtimeOnly "curse.maven:geckolib-388172:7009924"
+
// setup Xei (EMI/REI/JEI) using project property 'use_Xei'
switch (project.findProperty('use_Xei') ?: 'emi') {
case 'emi':
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java
new file mode 100644
index 0000000..0773388
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java
@@ -0,0 +1,38 @@
+package com.extendedae_plus.mixin.advancedae;
+
+import appeng.api.crafting.IPatternDetails;
+import com.extendedae_plus.content.ScaledProcessingPattern;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import java.util.List;
+
+/**适配
+ * Redirect PatternProviderLogic.pushPattern 中对 List.contains 的调用,
+ * 在遇到缩放样板时回退匹配到原始样板实例。
+ */
+@Mixin(value = AdvPatternProviderLogic.class, remap = false)
+public class AdvPatternProviderLogicContainsRedirectMixin {
+
+ @Redirect(method = "pushPattern",
+ at = @At(
+ value = "INVOKE",
+ target = "Ljava/util/List;contains(Ljava/lang/Object;)Z")
+ )
+ private boolean eap$patternsContains(List> list, Object o) {
+ try {
+ if (o instanceof ScaledProcessingPattern scaled) {
+ IPatternDetails base = scaled.getOriginal();
+ if (base != null && list.indexOf(base) != -1) {
+ return true;
+ }
+ }
+ // 使用 indexOf 避免再次触发对 List.contains 的 redirect(防止递归)
+ return list.indexOf(o) != -1;
+ } catch (Throwable t) {
+ return list.indexOf(o) != -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java
new file mode 100644
index 0000000..a75643c
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java
@@ -0,0 +1,14 @@
+package com.extendedae_plus.mixin.advancedae.accessor;
+
+import appeng.api.crafting.IPatternDetails;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import java.util.List;
+
+@Mixin(value = AdvPatternProviderLogic.class, remap = false)
+public interface AdvPatternProviderLogicPatternsAccessor {
+ @Accessor("patterns")
+ List eap$patterns();
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java
new file mode 100644
index 0000000..bd75482
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java
@@ -0,0 +1,12 @@
+package com.extendedae_plus.mixin.advancedae.accessor;
+
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+@Mixin(AdvPatternProviderMenu.class)
+public interface AdvPatternProviderMenuAdvancedAccessor {
+ @Accessor(value = "logic", remap = false)
+ AdvPatternProviderLogic eap$logic();
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java
new file mode 100644
index 0000000..2136f1a
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java
@@ -0,0 +1,139 @@
+package com.extendedae_plus.mixin.advancedae.client.gui;
+
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.SettingToggleButton;
+import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
+import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
+import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
+import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.player.Inventory;
+import net.pedroksl.advanced_ae.client.gui.AdvPatternProviderScreen;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
+import org.checkerframework.checker.units.qual.C;
+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 static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
+
+/**
+ * 为高级ae样板供应器界面添加“高级阻挡模式”按钮。
+ * - 位于左侧工具栏
+ * - 点击仅发送 C2S 切换请求;状态由 AE2 @GuiSync 回传决定
+ */
+@Mixin(AdvPatternProviderScreen.class)
+public abstract class AdvPatternProviderScreenMixin extends AEBaseScreen {
+
+ @Unique
+ private SettingToggleButton eap$AdvancedBlockingToggle;
+
+ @Unique
+ private boolean eap$AdvancedBlockingEnabled = false;
+
+ @Unique
+ private SettingToggleButton eap$SmartDoublingToggle;
+
+ @Unique
+ private boolean eap$SmartDoublingEnabled = false;
+
+ public AdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
+ super((AdvPatternProviderMenu) menu, playerInventory, title, style);
+ }
+
+ @Inject(method = "", at = @At("RETURN"))
+ private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
+ // 使用 @GuiSync 初始化
+ try {
+ if (menu instanceof PatternProviderMenuAdvancedSync sync) {
+ this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("Error initializing advanced sync", t);
+ }
+
+ // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡”
+ this.eap$AdvancedBlockingToggle = new SettingToggleButton<>(
+ Settings.BLOCKING_MODE,
+ this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
+ (btn, backwards) -> {
+ // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
+ var conn = Minecraft.getInstance().getConnection();
+ if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE);
+ }
+ ) {
+ @Override
+ public java.util.List getTooltipMessage() {
+ boolean enabled = eap$AdvancedBlockingEnabled;
+ var title = Component.literal("智能阻挡");
+ var line = enabled
+ ? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)")
+ : Component.literal("已禁用:这么好的功能为什么不打开呢");
+ return java.util.List.of(title, line);
+ }
+ };
+ // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
+ this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
+
+ this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
+
+ // 智能翻倍按钮:与高级阻挡同款样式,点击仅发送C2S,状态由@GuiSync驱动
+ try {
+ if (menu instanceof PatternProviderMenuDoublingSync sync2) {
+ this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("Error initializing smart doubling sync", t);
+ }
+
+ this.eap$SmartDoublingToggle = new SettingToggleButton<>(
+ Settings.BLOCKING_MODE,
+ this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
+ (btn, backwards) -> {
+ var conn = Minecraft.getInstance().getConnection();
+ if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE);
+ }
+ ) {
+ @Override
+ public java.util.List getTooltipMessage() {
+ boolean enabled = eap$SmartDoublingEnabled;
+ var title = Component.literal("智能翻倍");
+ var line = enabled
+ ? Component.literal("已启用:根据请求量对处理样板进行智能缩放")
+ : Component.literal("已禁用:按原始样板数量进行发配");
+ return java.util.List.of(title, line);
+ }
+ };
+
+ this.eap$SmartDoublingToggle.set(this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO);
+ this.addToLeftToolbar(this.eap$SmartDoublingToggle);
+ }
+
+ // 每帧刷新:仅从菜单(@GuiSync)同步布尔值,保持按钮状态一致
+ @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
+ private void eap$updateAdvancedBlocking(CallbackInfo ci) {
+ if (this.eap$AdvancedBlockingToggle != null) {
+ boolean desired = this.eap$AdvancedBlockingEnabled;
+ if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
+ desired = sync.eap$getAdvancedBlockingSynced();
+ }
+ this.eap$AdvancedBlockingEnabled = desired;
+ this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
+ }
+
+ if (this.eap$SmartDoublingToggle != null) {
+ boolean desired2 = this.eap$SmartDoublingEnabled;
+ if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
+ desired2 = sync2.eap$getSmartDoublingSynced();
+ }
+ this.eap$SmartDoublingEnabled = desired2;
+ this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
+ }
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java
new file mode 100644
index 0000000..d7be718
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java
@@ -0,0 +1,139 @@
+package com.extendedae_plus.mixin.advancedae.client.gui;
+
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.SettingToggleButton;
+import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
+import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
+import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
+import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.player.Inventory;
+import net.pedroksl.advanced_ae.client.gui.SmallAdvPatternProviderScreen;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.SmallAdvPatternProviderMenu;
+import org.checkerframework.checker.units.qual.C;
+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 static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
+
+/**
+ * 为高级ae样板供应器界面添加“高级阻挡模式”按钮。
+ * - 位于左侧工具栏
+ * - 点击仅发送 C2S 切换请求;状态由 AE2 @GuiSync 回传决定
+ */
+@Mixin(SmallAdvPatternProviderScreen.class)
+public abstract class SmallAdvPatternProviderScreenMixin extends AEBaseScreen {
+
+ @Unique
+ private SettingToggleButton eap$AdvancedBlockingToggle;
+
+ @Unique
+ private boolean eap$AdvancedBlockingEnabled = false;
+
+ @Unique
+ private SettingToggleButton eap$SmartDoublingToggle;
+
+ @Unique
+ private boolean eap$SmartDoublingEnabled = false;
+
+ public SmallAdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
+ super((SmallAdvPatternProviderMenu) menu, playerInventory, title, style);
+ }
+
+ @Inject(method = "", at = @At("RETURN"))
+ private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
+ // 使用 @GuiSync 初始化
+ try {
+ if (menu instanceof PatternProviderMenuAdvancedSync sync) {
+ this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("Error initializing advanced sync", t);
+ }
+
+ // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡”
+ this.eap$AdvancedBlockingToggle = new SettingToggleButton<>(
+ Settings.BLOCKING_MODE,
+ this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
+ (btn, backwards) -> {
+ // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
+ var conn = Minecraft.getInstance().getConnection();
+ if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE);
+ }
+ ) {
+ @Override
+ public java.util.List getTooltipMessage() {
+ boolean enabled = eap$AdvancedBlockingEnabled;
+ var title = Component.literal("智能阻挡");
+ var line = enabled
+ ? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)")
+ : Component.literal("已禁用:这么好的功能为什么不打开呢");
+ return java.util.List.of(title, line);
+ }
+ };
+ // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
+ this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
+
+ this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
+
+ // 智能翻倍按钮:与高级阻挡同款样式,点击仅发送C2S,状态由@GuiSync驱动
+ try {
+ if (menu instanceof PatternProviderMenuDoublingSync sync2) {
+ this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("Error initializing smart doubling sync", t);
+ }
+
+ this.eap$SmartDoublingToggle = new SettingToggleButton<>(
+ Settings.BLOCKING_MODE,
+ this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
+ (btn, backwards) -> {
+ var conn = Minecraft.getInstance().getConnection();
+ if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE);
+ }
+ ) {
+ @Override
+ public java.util.List getTooltipMessage() {
+ boolean enabled = eap$SmartDoublingEnabled;
+ var title = Component.literal("智能翻倍");
+ var line = enabled
+ ? Component.literal("已启用:根据请求量对处理样板进行智能缩放")
+ : Component.literal("已禁用:按原始样板数量进行发配");
+ return java.util.List.of(title, line);
+ }
+ };
+
+ this.eap$SmartDoublingToggle.set(this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO);
+ this.addToLeftToolbar(this.eap$SmartDoublingToggle);
+ }
+
+ // 每帧刷新:仅从菜单(@GuiSync)同步布尔值,保持按钮状态一致
+ @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
+ private void eap$updateAdvancedBlocking(CallbackInfo ci) {
+ if (this.eap$AdvancedBlockingToggle != null) {
+ boolean desired = this.eap$AdvancedBlockingEnabled;
+ if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
+ desired = sync.eap$getAdvancedBlockingSynced();
+ }
+ this.eap$AdvancedBlockingEnabled = desired;
+ this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
+ }
+
+ if (this.eap$SmartDoublingToggle != null) {
+ boolean desired2 = this.eap$SmartDoublingEnabled;
+ if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
+ desired2 = sync2.eap$getSmartDoublingSynced();
+ }
+ this.eap$SmartDoublingEnabled = desired2;
+ this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
+ }
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java
new file mode 100644
index 0000000..0cce26d
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java
@@ -0,0 +1,94 @@
+package com.extendedae_plus.mixin.advancedae.helpers;
+
+import appeng.api.crafting.IPatternDetails;
+import appeng.api.crafting.IPatternDetails.IInput;
+import appeng.api.stacks.AEKey;
+import appeng.api.stacks.GenericStack;
+import appeng.helpers.patternprovider.PatternProviderTarget;
+import com.extendedae_plus.api.AdvancedBlockingHolder;
+import net.minecraft.core.HolderLookup;
+import net.minecraft.nbt.CompoundTag;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+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.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.Collections;
+
+@Mixin(value = AdvPatternProviderLogic.class, remap = false)
+public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
+ @Unique
+ private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking";
+
+ @Unique
+ private boolean eap$advancedBlocking = false;
+
+ @Override
+ public boolean eap$getAdvancedBlocking() {
+ return eap$advancedBlocking;
+ }
+
+ @Override
+ public void eap$setAdvancedBlocking(boolean value) {
+ this.eap$advancedBlocking = value;
+ }
+
+ @Inject(method = "writeToNBT", at = @At("TAIL"))
+ private void eap$writeAdvancedToNbt(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
+ tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
+ }
+
+ @Inject(method = "readFromNBT", at = @At("TAIL"))
+ private void eap$readAdvancedFromNbt(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
+ if (tag.contains(EAP_ADV_BLOCKING_KEY)) {
+ this.eap$advancedBlocking = tag.getBoolean(EAP_ADV_BLOCKING_KEY);
+ }
+ }
+
+ // 在 pushPattern 中,重定向对 adapter.containsPatternInput(...) 的调用
+ @Redirect(method = "pushPattern", at = @At(value = "INVOKE", target = "Lappeng/helpers/patternprovider/PatternProviderTarget;containsPatternInput(Ljava/util/Set;)Z"))
+ private boolean eap$redirectBlockingContains(PatternProviderTarget adapter,
+ java.util.Set patternInputs,
+ IPatternDetails patternDetails,
+ appeng.api.stacks.KeyCounter[] inputHolder) {
+ // 原版是否打开阻挡
+ boolean vanillaBlocking = ((AdvPatternProviderLogic)(Object)this).isBlocking();
+ if (!vanillaBlocking) {
+ return adapter.containsPatternInput(patternInputs);
+ }
+
+ // 仅当高级阻挡启用时启用“匹配则不阻挡”
+ if (this.eap$advancedBlocking) {
+ if (eap$targetFullyMatchesPatternInputs(adapter, patternDetails)) {
+ // 返回 false 表示“不包含阻挡关键物”,从而不触发 continue,允许发配
+ return false;
+ }
+ }
+ // 否则使用原判定
+ return adapter.containsPatternInput(patternInputs);
+ }
+
+ @Unique
+ private boolean eap$targetFullyMatchesPatternInputs(PatternProviderTarget adapter, IPatternDetails patternDetails) {
+ for (IInput in : patternDetails.getInputs()) {
+ boolean slotMatched = false;
+ for (GenericStack candidate : in.getPossibleInputs()) {
+ AEKey key = candidate.what().dropSecondary();
+ if (adapter.containsPatternInput(Collections.singleton(key))) {
+ slotMatched = true;
+ break;
+ }
+ }
+ if (!slotMatched) {
+ return false; // 任一输入槽未匹配则失败
+ }
+ }
+ return true; // 每个输入槽都至少匹配了一个候选输入
+ }
+
+ @Shadow public void saveChanges() {}
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java
new file mode 100644
index 0000000..e3f3b3e
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java
@@ -0,0 +1,76 @@
+package com.extendedae_plus.mixin.advancedae.helpers;
+
+import appeng.api.crafting.IPatternDetails;
+import appeng.crafting.pattern.AEProcessingPattern;
+import com.extendedae_plus.api.SmartDoublingAwarePattern;
+import com.extendedae_plus.api.SmartDoublingHolder;
+import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor;
+import net.minecraft.core.HolderLookup;
+import net.minecraft.nbt.CompoundTag;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+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 = AdvPatternProviderLogic.class, remap = false)
+public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder {
+ @Unique
+ private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling";
+
+ @Unique
+ private boolean eap$smartDoubling = false;
+
+ @Override
+ public boolean eap$getSmartDoubling() {
+ return eap$smartDoubling;
+ }
+
+ @Override
+ public void eap$setSmartDoubling(boolean value) {
+ this.eap$smartDoubling = value;
+ // 立即将开关状态应用到当前 Provider 的样板上,避免等待下一次 updatePatterns
+ try {
+ var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
+ for (IPatternDetails details : list) {
+ if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) {
+ aware.eap$setAllowScaling(value);
+ }
+ }
+ // 触发一次刷新,让网络及时拿到最新状态(也会触发 ICraftingProvider.requestUpdate(mainNode))
+ ((AdvPatternProviderLogic) (Object) this).updatePatterns();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Inject(method = "writeToNBT", at = @At("TAIL"))
+ private void eap$writeSmartDoublingToNbt(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
+ tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
+ }
+
+ @Inject(method = "readFromNBT", at = @At("TAIL"))
+ private void eap$readSmartDoublingFromNbt(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
+ if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
+ this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY);
+ }
+ }
+
+ @Inject(method = "updatePatterns", at = @At("TAIL"))
+ private void eap$applySmartDoublingToPatterns(CallbackInfo ci) {
+ try {
+ var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
+ boolean allow = this.eap$smartDoubling;
+ for (IPatternDetails details : list) {
+ if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) {
+ aware.eap$setAllowScaling(allow);
+ }
+ }
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Shadow
+ public void saveChanges() {}
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java
new file mode 100644
index 0000000..65e8b9d
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java
@@ -0,0 +1,43 @@
+package com.extendedae_plus.mixin.advancedae.menu;
+
+import appeng.menu.AEBaseMenu;
+import appeng.menu.guisync.GuiSync;
+import com.extendedae_plus.api.AdvancedBlockingHolder;
+import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
+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(AdvPatternProviderMenu.class)
+public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync {
+ @Final
+ @Shadow(remap = false)
+ protected AdvPatternProviderLogic logic;
+
+ // 选择一个未占用的 GUI 同步 id(AE2 已用到 7),这里使用 21 以避冲突
+ @Unique
+ @GuiSync(22)
+ public boolean eap$AdvancedBlocking = false;
+
+ @Inject(method = "broadcastChanges", at = @At("HEAD"))
+ private void eap$syncAdvancedBlocking(CallbackInfo ci) {
+ // 避免@Shadow父类方法,改用公共API:AEBaseMenu#isClientSide()
+ if (!((AEBaseMenu) (Object) this).isClientSide()) {
+ var l = this.logic;
+ if (l instanceof AdvancedBlockingHolder holder) {
+ this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
+ }
+ }
+ }
+
+ @Override
+ public boolean eap$getAdvancedBlockingSynced() {
+ return this.eap$AdvancedBlocking;
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java
new file mode 100644
index 0000000..b5f0493
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java
@@ -0,0 +1,41 @@
+package com.extendedae_plus.mixin.advancedae.menu;
+
+import appeng.menu.AEBaseMenu;
+import appeng.menu.guisync.GuiSync;
+import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
+import com.extendedae_plus.api.SmartDoublingHolder;
+import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
+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(AdvPatternProviderMenu.class)
+public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
+ @Final
+ @Shadow(remap = false)
+ protected AdvPatternProviderLogic logic;
+
+ @Unique
+ @GuiSync(23)
+ public boolean eap$SmartDoubling = false;
+
+ @Inject(method = "broadcastChanges", at = @At("HEAD"))
+ private void eap$syncSmartDoubling(CallbackInfo ci) {
+ if (!((AEBaseMenu) (Object) this).isClientSide()) {
+ var l = this.logic;
+ if (l instanceof SmartDoublingHolder holder) {
+ this.eap$SmartDoubling = holder.eap$getSmartDoubling();
+ }
+ }
+ }
+
+ @Override
+ public boolean eap$getSmartDoublingSynced() {
+ return this.eap$SmartDoubling;
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java
index 3990ae3..cb00349 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java
@@ -8,7 +8,6 @@ import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderTarget;
import com.extendedae_plus.api.AdvancedBlockingHolder;
import net.minecraft.nbt.CompoundTag;
-import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -22,7 +21,7 @@ import java.util.Collections;
@Mixin(value = PatternProviderLogic.class, remap = false)
public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
@Unique
- private static final String EPP_ADV_BLOCKING_KEY = "epp_advanced_blocking";
+ private static final String EAP_ADV_BLOCKING_KEY = "epp_advanced_blocking";
@Unique
private boolean eap$advancedBlocking = false;
@@ -39,13 +38,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
@Inject(method = "writeToNBT", at = @At("TAIL"))
private void eap$writeAdvancedToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
- tag.putBoolean(EPP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
+ tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
}
@Inject(method = "readFromNBT", at = @At("TAIL"))
private void eap$readAdvancedFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
- if (tag.contains(EPP_ADV_BLOCKING_KEY)) {
- this.eap$advancedBlocking = tag.getBoolean(EPP_ADV_BLOCKING_KEY);
+ if (tag.contains(EAP_ADV_BLOCKING_KEY)) {
+ this.eap$advancedBlocking = tag.getBoolean(EAP_ADV_BLOCKING_KEY);
}
}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java
index ad455d6..60b65ec 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java
@@ -7,7 +7,6 @@ import com.extendedae_plus.api.SmartDoublingAwarePattern;
import com.extendedae_plus.api.SmartDoublingHolder;
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicPatternsAccessor;
import net.minecraft.nbt.CompoundTag;
-import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -18,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = PatternProviderLogic.class, remap = false)
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Unique
- private static final String EPP_SMART_DOUBLING_KEY = "epp_smart_doubling";
+ private static final String EAP_SMART_DOUBLING_KEY = "epp_smart_doubling";
@Unique
private boolean eap$smartDoubling = false;
@@ -47,13 +46,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Inject(method = "writeToNBT", at = @At("TAIL"))
private void eap$writeSmartDoublingToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
- tag.putBoolean(EPP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
+ tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
}
@Inject(method = "readFromNBT", at = @At("TAIL"))
private void eap$readSmartDoublingFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
- if (tag.contains(EPP_SMART_DOUBLING_KEY)) {
- this.eap$smartDoubling = tag.getBoolean(EPP_SMART_DOUBLING_KEY);
+ if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
+ this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY);
}
}
diff --git a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java b/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java
index c64655f..1c057e3 100644
--- a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java
+++ b/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java
@@ -1,17 +1,19 @@
package com.extendedae_plus.network;
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
import appeng.menu.implementations.PatternProviderMenu;
-import com.extendedae_plus.api.AdvancedBlockingHolder;
-import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.api.AdvancedBlockingHolder;
+import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
+import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
-import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.minecraft.server.level.ServerPlayer;
-import appeng.api.config.Settings;
-import appeng.api.config.YesNo;
+import net.neoforged.neoforge.network.handling.IPayloadContext;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
/**
* C2S:切换高级阻挡模式。
@@ -36,16 +38,31 @@ public class ToggleAdvancedBlockingC2SPacket implements CustomPacketPayload {
public static void handle(final ToggleAdvancedBlockingC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
- if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
-
- var accessor = (PatternProviderMenuAdvancedAccessor) menu;
- var logic = accessor.eap$logic();
- if (logic instanceof AdvancedBlockingHolder holder) {
- boolean next = !holder.eap$getAdvancedBlocking();
- holder.eap$setAdvancedBlocking(next);
- // 自动开启原版阻挡
- logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
- logic.saveChanges();
+ var containerMenu = player.containerMenu;
+ if (containerMenu instanceof PatternProviderMenu menu) {
+ var accessor = (PatternProviderMenuAdvancedAccessor) menu;
+ var logic = accessor.eap$logic();
+ if (logic instanceof AdvancedBlockingHolder holder) {
+ boolean current = holder.eap$getAdvancedBlocking();
+ boolean next = !current;
+ holder.eap$setAdvancedBlocking(next);
+ // 自动开启原版阻挡
+ logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
+ // 保存并触发 AE2 的菜单 @GuiSync 广播到所有观看该菜单的玩家
+ logic.saveChanges();
+ }
+ }else if (containerMenu instanceof AdvPatternProviderMenu menu){
+ var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu;
+ var logic = accessor.eap$logic();
+ if (logic instanceof AdvancedBlockingHolder holder) {
+ boolean current = holder.eap$getAdvancedBlocking();
+ boolean next = !current;
+ holder.eap$setAdvancedBlocking(next);
+ // 自动开启原版阻挡
+ logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
+ // 保存并触发 AE2 的菜单 @GuiSync 广播到所有观看该菜单的玩家
+ logic.saveChanges();
+ }
}
});
}
diff --git a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java b/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java
index 2e2bd82..79f6664 100644
--- a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java
+++ b/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java
@@ -1,15 +1,17 @@
package com.extendedae_plus.network;
import appeng.menu.implementations.PatternProviderMenu;
-import com.extendedae_plus.api.SmartDoublingHolder;
-import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.api.SmartDoublingHolder;
+import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
+import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
-import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.minecraft.server.level.ServerPlayer;
+import net.neoforged.neoforge.network.handling.IPayloadContext;
+import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
/**
* C2S:切换智能翻倍启用状态。
@@ -34,14 +36,25 @@ public class ToggleSmartDoublingC2SPacket implements CustomPacketPayload {
public static void handle(final ToggleSmartDoublingC2SPacket msg, final IPayloadContext ctx) {
ctx.enqueueWork(() -> {
if (!(ctx.player() instanceof ServerPlayer player)) return;
- if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
-
- var accessor = (PatternProviderMenuAdvancedAccessor) menu;
- var logic = accessor.eap$logic();
- if (logic instanceof SmartDoublingHolder holder) {
- boolean next = !holder.eap$getSmartDoubling();
- holder.eap$setSmartDoubling(next);
- logic.saveChanges();
+ var containerMenu = player.containerMenu;
+ if (containerMenu instanceof PatternProviderMenu menu) {
+ var accessor = (PatternProviderMenuAdvancedAccessor) menu;
+ var logic = accessor.eap$logic();
+ if (logic instanceof SmartDoublingHolder holder) {
+ boolean current = holder.eap$getSmartDoubling();
+ boolean next = !current;
+ holder.eap$setSmartDoubling(next);
+ logic.saveChanges();
+ }
+ }else if (containerMenu instanceof AdvPatternProviderMenu menu){
+ var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu;
+ var logic = accessor.eap$logic();
+ if (logic instanceof SmartDoublingHolder holder) {
+ boolean current = holder.eap$getSmartDoubling();
+ boolean next = !current;
+ holder.eap$setSmartDoubling(next);
+ logic.saveChanges();
+ }
}
});
}
diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json
index 314e662..f7a85bd 100644
--- a/src/main/resources/extendedae_plus.mixins.json
+++ b/src/main/resources/extendedae_plus.mixins.json
@@ -3,6 +3,13 @@
"package": "com.extendedae_plus.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
+ "advancedae.AdvPatternProviderLogicContainsRedirectMixin",
+ "advancedae.accessor.AdvPatternProviderLogicPatternsAccessor",
+ "advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor",
+ "advancedae.helpers.AdvPatternProviderLogicAdvancedMixin",
+ "advancedae.helpers.AdvPatternProviderLogicDoublingMixin",
+ "advancedae.menu.AdvPatternProviderMenuAdvancedMixin",
+ "advancedae.menu.AdvPatternProviderMenuDoublingMixin",
"ae2.AEProcessingPatternMixin",
"ae2.CraftingCalculationMixin",
"ae2.CraftingCPUClusterMixin",
@@ -37,6 +44,8 @@
"PickFromWirelessMixin",
"accessor.AbstractContainerScreenAccessor",
"accessor.ScreenAccessor",
+ "advancedae.client.gui.AdvPatternProviderScreenMixin",
+ "advancedae.client.gui.SmallAdvPatternProviderScreenMixin",
"ae2.accessor.AEBaseScreenAccessor",
"ae2.accessor.AEBaseScreenInvoker",
"ae2.accessor.MEStorageScreenAccessor",
From 3968b262173e1cc88ac34bc1705e2518fba80e39 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sat, 20 Sep 2025 23:29:10 +0800
Subject: [PATCH 07/43] =?UTF-8?q?fix:=20=E5=90=88=E6=88=90=E8=AE=A1?=
=?UTF-8?q?=E5=88=92=E7=95=8C=E9=9D=A2=E6=B7=BB=E5=8A=A0=E7=BC=BA=E5=B0=91?=
=?UTF-8?q?=E7=89=A9=E5=93=81=E9=81=87=E5=88=B0=E6=B5=81=E4=BD=93=EF=BC=8C?=
=?UTF-8?q?mek=E5=8C=96=E5=AD=A6=E5=93=81=E4=BC=9A=E5=87=BA=E7=8E=B0?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E4=B9=A6=E7=AD=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../integration/jei/JeiRuntimeProxy.java | 83 +++++++++++++++++++
.../ae2/menu/CraftConfirmMenuGoBackMixin.java | 28 +++++--
2 files changed, 104 insertions(+), 7 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java
index b5e76f7..d5f2de3 100644
--- a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java
+++ b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java
@@ -2,7 +2,9 @@ package com.extendedae_plus.integration.jei;
import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor;
import mezz.jei.api.constants.VanillaTypes;
+import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.ITypedIngredient;
+import mezz.jei.api.neoforge.NeoForgeTypes;
import mezz.jei.api.runtime.IBookmarkOverlay;
import mezz.jei.api.runtime.IIngredientListOverlay;
import mezz.jei.api.runtime.IJeiRuntime;
@@ -10,8 +12,12 @@ import mezz.jei.gui.bookmarks.BookmarkList;
import mezz.jei.gui.bookmarks.IngredientBookmark;
import mezz.jei.gui.overlay.elements.IElement;
import net.minecraft.world.item.ItemStack;
+import net.neoforged.fml.ModList;
+import net.neoforged.neoforge.fluids.FluidStack;
import javax.annotation.Nullable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -155,6 +161,83 @@ public final class JeiRuntimeProxy {
}
}
+ public static void addBookmark(FluidStack fluidStack) {
+ IJeiRuntime rt = RUNTIME;
+ if (rt == null) return;
+
+ IBookmarkOverlay overlay = rt.getBookmarkOverlay();
+ if (overlay instanceof BookmarkOverlayAccessor accessor) {
+ BookmarkList list = accessor.eap$getBookmarkList();
+ Optional> typedOpt = rt.getIngredientManager()
+ .createTypedIngredient(NeoForgeTypes.FLUID_STACK, fluidStack);
+ typedOpt.ifPresent(typed -> {
+ IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
+ list.add(bookmark); // add 内部会自动保存到配置
+ });
+ }
+ }
+
+ /**
+ * 如果存在 Mekanism/appmek,则将 Mekanism 化学堆栈添加到 JEI 书签。
+ */
+ public static void addBookmark(Object chemicalStack) {
+ if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return;
+
+ IJeiRuntime rt = RUNTIME;
+ if (rt == null) return;
+
+ IBookmarkOverlay overlay = rt.getBookmarkOverlay();
+ if (overlay instanceof BookmarkOverlayAccessor accessor) {
+ BookmarkList list = accessor.eap$getBookmarkList();
+ try {
+ if (chemicalStack == null) return;
+
+ // Determine Mekanism JEI ingredient type constant by runtime class name
+ String clsName = chemicalStack.getClass().getName();
+ String mekanismJeiClass = "mekanism.client.recipe_viewer.jei.MekanismJEI";
+ Class> jeiCls = Class.forName(mekanismJeiClass);
+ Field typeField = null;
+ if ("mekanism.api.chemical.ChemicalStack".equals(clsName)) {
+ typeField = jeiCls.getField("TYPE_CHEMICAL");
+ }
+ if (typeField == null) return;
+ Object typeConst = typeField.get(null);
+
+ // Use ingredient manager reflectively to create a typed ingredient
+ Object ingredientManager = rt.getIngredientManager();
+ Method createTypedIngredient = ingredientManager.getClass().getMethod("createTypedIngredient", IIngredientType.class, Object.class);
+ Object opt = createTypedIngredient.invoke(ingredientManager, typeConst, chemicalStack);
+ if (!(opt instanceof Optional> typedOpt)) return;
+ if (typedOpt.isPresent()) {
+ Object typed = typedOpt.get();
+ // Find a compatible static create(...) method on IngredientBookmark where
+ // the second parameter is assignable from the actual ingredientManager instance.
+ Method createMethod = null;
+ for (Method m : IngredientBookmark.class.getMethods()) {
+ if (!m.getName().equals("create")) continue;
+ Class>[] params = m.getParameterTypes();
+ if (params.length != 2) continue;
+ // first param should accept the typed ingredient
+ boolean firstOk = params[0].isAssignableFrom(typed.getClass()) || params[0].isAssignableFrom(ITypedIngredient.class);
+ boolean secondOk = params[1].isAssignableFrom(ingredientManager.getClass());
+ if (firstOk && secondOk) {
+ createMethod = m;
+ break;
+ }
+ }
+ if (createMethod != null) {
+ Object bookmark = createMethod.invoke(null, typed, ingredientManager);
+ if (bookmark != null) {
+ list.add((IngredientBookmark) bookmark);
+ }
+ }
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
/**
* 从 JEI 书签移除物品
*/
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java
index 9280311..02a24eb 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java
@@ -1,10 +1,13 @@
package com.extendedae_plus.mixin.ae2.menu;
+import appeng.api.stacks.AEFluidKey;
+import appeng.api.stacks.AEItemKey;
import appeng.menu.me.crafting.CraftConfirmMenu;
import appeng.menu.me.crafting.CraftingPlanSummary;
import appeng.menu.me.crafting.CraftingPlanSummaryEntry;
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
import net.minecraft.client.gui.screens.Screen;
+import net.neoforged.fml.ModList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -42,13 +45,24 @@ public class CraftConfirmMenuGoBackMixin {
// 为缺失的条目添加 JEI 书签
for (CraftingPlanSummaryEntry entry : entries) {
if (entry.getMissingAmount() > 0) {
- try {
- var display = entry.getWhat().wrapForDisplayOrFilter();
- if (display != null && !display.isEmpty()) {
- // wrapForDisplayOrFilter 返回 ItemStack
- JeiRuntimeProxy.addBookmark(display);
- }
- } catch (Throwable ignored) {}
+ var what = entry.getWhat();
+ if (what instanceof AEItemKey aeItemKey) {
+ JeiRuntimeProxy.addBookmark(aeItemKey.getReadOnlyStack());
+ } else if (what instanceof AEFluidKey aeFluidKey) {
+ JeiRuntimeProxy.addBookmark(aeFluidKey.toStack(1000));
+ } else if (ModList.get().isLoaded("appmek") && ModList.get().isLoaded("mekanism")) {
+ try {
+ if (what != null) {
+ // avoid compile-time dependency on MekanismKey by reflection
+ Class> mekanismKeyCls = Class.forName("me.ramidzkh.mekae2.ae2.MekanismKey");
+ if (mekanismKeyCls.isInstance(what)) {
+ java.lang.reflect.Method m = mekanismKeyCls.getMethod("getStack");
+ Object stack = m.invoke(what);
+ JeiRuntimeProxy.addBookmark(stack);
+ }
+ }
+ } catch (Throwable ignored) {}
+ }
}
}
} catch (Throwable ignored) {}
From 10afb16acbaf1f48d4f20638ef186c2e991726f6 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sun, 21 Sep 2025 01:57:28 +0800
Subject: [PATCH 08/43] =?UTF-8?q?feat:=20=E7=A7=BB=E6=A4=8D1.20=E7=9A=84?=
=?UTF-8?q?=E5=AE=9E=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 5 +-
.../com/extendedae_plus/ExtendedAEPlus.java | 14 +-
.../upgrades/EntitySpeedCardItem.java | 80 +++++
.../ae/items/EntitySpeedTickerPartItem.java | 25 ++
.../ae/menu/EntitySpeedTickerMenu.java | 151 +++++++++
.../ae/parts/EntitySpeedTickerPart.java | 309 ++++++++++++++++++
.../ae/screen/EntitySpeedTickerScreen.java | 136 ++++++++
.../extendedae_plus/client/ClientProxy.java | 33 +-
.../client/ui/ProviderSelectScreen.java | 1 -
.../extendedae_plus/config/ModConfigs.java | 57 +++-
.../extendedae_plus/init/ModCreativeTabs.java | 8 +-
.../com/extendedae_plus/init/ModItems.java | 38 ++-
.../extendedae_plus/init/ModMenuTypes.java | 15 +-
.../{network => init}/ModNetwork.java | 4 +-
.../extendedae_plus/init/UpgradeCards.java | 17 +
.../integration/jei/ExtendedAEJeiPlugin.java | 31 ++
.../ae2/client/gui/AEBaseScreenMixin.java | 1 -
.../network/ToggleEntityTickerC2SPacket.java | 50 +++
.../util/ConfigParsingUtils.java | 172 ++++++++++
.../com/extendedae_plus/util/PowerUtils.java | 162 +++++++++
.../ae2/screens/entity_speed_ticker.json | 78 +++++
.../ae2/textures/guis/blank_background.png | Bin 0 -> 1776 bytes
.../assets/extendedae_plus/lang/zh_cn.json | 25 ++
.../models/item/entity_speed_card.json | 14 +
.../models/item/entity_speed_card_x16.json | 6 +
.../models/item/entity_speed_card_x2.json | 6 +
.../models/item/entity_speed_card_x4.json | 6 +
.../models/item/entity_speed_card_x8.json | 6 +
.../models/item/entity_speed_ticker.json | 7 +
.../part/entity_speed_ticker_has_channel.json | 3 +
.../models/part/entity_speed_ticker_off.json | 3 +
.../models/part/entity_speed_ticker_on.json | 3 +
.../models/part/entity_speed_ticker_part.json | 9 +
.../textures/item/entity_speed_card_x16.png | Bin 0 -> 2485 bytes
.../item/entity_speed_card_x16.png.mcmeta | 15 +
.../textures/item/entity_speed_card_x2.png | Bin 0 -> 2271 bytes
.../item/entity_speed_card_x2.png.mcmeta | 15 +
.../textures/item/entity_speed_card_x4.png | Bin 0 -> 2501 bytes
.../item/entity_speed_card_x4.png.mcmeta | 15 +
.../textures/item/entity_speed_card_x8.png | Bin 0 -> 2476 bytes
.../item/entity_speed_card_x8.png.mcmeta | 15 +
.../part/entity_speed_ticker_back.png | Bin 0 -> 4001 bytes
.../part/entity_speed_ticker_font.png | Bin 0 -> 3846 bytes
.../part/entity_speed_ticker_sides.png | Bin 0 -> 4023 bytes
.../recipe/entity_speed_card_x16.json | 37 +++
.../recipe/entity_speed_card_x2.json | 30 ++
.../recipe/entity_speed_card_x4.json | 34 ++
.../recipe/entity_speed_card_x8.json | 34 ++
.../recipe/entity_speed_ticker.json | 33 ++
49 files changed, 1668 insertions(+), 35 deletions(-)
create mode 100644 src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
create mode 100644 src/main/java/com/extendedae_plus/ae/items/EntitySpeedTickerPartItem.java
create mode 100644 src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java
create mode 100644 src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java
create mode 100644 src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java
rename src/main/java/com/extendedae_plus/{network => init}/ModNetwork.java (94%)
create mode 100644 src/main/java/com/extendedae_plus/init/UpgradeCards.java
create mode 100644 src/main/java/com/extendedae_plus/network/ToggleEntityTickerC2SPacket.java
create mode 100644 src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java
create mode 100644 src/main/java/com/extendedae_plus/util/PowerUtils.java
create mode 100644 src/main/resources/assets/ae2/screens/entity_speed_ticker.json
create mode 100644 src/main/resources/assets/ae2/textures/guis/blank_background.png
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_card.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x16.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x2.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x4.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x8.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/entity_speed_ticker.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_has_channel.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_off.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_on.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_part.json
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x16.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x16.png.mcmeta
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x2.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x2.png.mcmeta
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png.mcmeta
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x8.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x8.png.mcmeta
create mode 100644 src/main/resources/assets/extendedae_plus/textures/part/entity_speed_ticker_back.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/part/entity_speed_ticker_font.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/part/entity_speed_ticker_sides.png
create mode 100644 src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x16.json
create mode 100644 src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x2.json
create mode 100644 src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x4.json
create mode 100644 src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x8.json
create mode 100644 src/main/resources/data/extendedae_plus/recipe/entity_speed_ticker.json
diff --git a/build.gradle b/build.gradle
index 8bbce35..7394979 100644
--- a/build.gradle
+++ b/build.gradle
@@ -159,7 +159,10 @@ dependencies {
runtimeOnly "de.mari_023:ae2wtlib:19.2.1"
runtimeOnly "curse.maven:jade-324717:5427817"
runtimeOnly "curse.maven:mega-cells-622112:6005043"
- // runtimeOnly "mekanism:Mekanism:1.21.1-10.7.0.55"
+
+ runtimeOnly "curse.maven:mekanism-268560:6895130"
+ runtimeOnly "curse.maven:applied-mekanistics-574300:5978711"
+
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
//aea
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 6670527..3f6640a 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -1,10 +1,12 @@
package com.extendedae_plus;
+import appeng.api.parts.IPart;
+import appeng.api.parts.PartModels;
import appeng.block.AEBaseEntityBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
+import appeng.items.parts.PartModelsHelper;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.*;
-import com.extendedae_plus.network.ModNetwork;
import com.mojang.logging.LogUtils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
@@ -69,6 +71,16 @@ public class ExtendedAEPlus {
// 绑定 AE2 的 CraftingBlockEntity 到本模组的自定义加速器方块,避免 AEBaseEntityBlock.blockEntityType 为空
event.enqueueWork(() -> {
try {
+ // 注册升级卡
+ new UpgradeCards(event);
+
+ // 为 PartItem 注册 AE2 部件模型
+ PartModels.registerModels(
+ PartModelsHelper.createModels(
+ ModItems.ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
+ )
+ );
+
// 注册自定义 AE2 MenuLocator(用于 Curios 槽位打开菜单)
try {
appeng.menu.locator.MenuLocators.register(
diff --git a/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java b/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
new file mode 100644
index 0000000..fe253ae
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
@@ -0,0 +1,80 @@
+package com.extendedae_plus.ae.definitions.upgrades;
+
+import appeng.items.materials.UpgradeCardItem;
+import com.extendedae_plus.init.ModItems;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.TooltipFlag;
+import net.minecraft.world.item.component.CustomData;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.api.distmarker.OnlyIn;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * 单一的实体加速卡 Item,通过 ItemStack 的 NBT 存储 exponent(0/1/2/3)来区分等级
+ */
+public class EntitySpeedCardItem extends UpgradeCardItem {
+ public static final String NBT_MULTIPLIER = "EAS:mult";
+
+ public EntitySpeedCardItem(Properties props) {
+ super(props);
+ }
+
+ public static ItemStack withMultiplier(int multiplier) {
+ ItemStack stack = new ItemStack(ModItems.ENTITY_SPEED_CARD.get());
+ CompoundTag tag = new CompoundTag();
+ tag.putInt(NBT_MULTIPLIER, multiplier);
+ stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
+ return stack;
+ }
+
+ public static int readMultiplier(ItemStack stack) {
+ if (stack == null || stack.isEmpty()) return 1;
+ CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
+ if (customData == null || !customData.copyTag().contains(NBT_MULTIPLIER)) return 1;
+ return customData.copyTag().getInt(NBT_MULTIPLIER);
+ }
+
+ @Override
+ public @NotNull Component getName(@NotNull ItemStack stack) {
+ int mult = readMultiplier(stack);
+ String key;
+ switch (mult) {
+ case 2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
+ case 4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
+ case 8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
+ case 16 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x16";
+ default -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x1";
+ }
+ return Component.translatable(key);
+ }
+
+
+ public List getTooltipLines(ItemStack stack) {
+ int mult = readMultiplier(stack);
+ long cap = 1L;
+ switch (mult) {
+ case 16 -> cap = 1024L;
+ case 8 -> cap = 256L;
+ case 4 -> cap = 64L;
+ case 2 -> cap = 8L;
+ }
+ MutableComponent line1 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.multiplier", "x" + mult);
+ MutableComponent line2 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.max", cap);
+ return List.of(line1, line2);
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ @Override
+ public void appendHoverText(ItemStack stack, TooltipContext context, List lines, TooltipFlag advancedTooltips) {
+ super.appendHoverText(stack, context, lines, advancedTooltips);
+ lines.addAll(this.getTooltipLines(stack));
+ }
+}
+
+
diff --git a/src/main/java/com/extendedae_plus/ae/items/EntitySpeedTickerPartItem.java b/src/main/java/com/extendedae_plus/ae/items/EntitySpeedTickerPartItem.java
new file mode 100644
index 0000000..ccee587
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/items/EntitySpeedTickerPartItem.java
@@ -0,0 +1,25 @@
+package com.extendedae_plus.ae.items;
+
+import appeng.items.parts.PartItem;
+import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.TooltipFlag;
+
+import java.util.List;
+
+
+public class EntitySpeedTickerPartItem extends PartItem {
+ public EntitySpeedTickerPartItem(Properties properties) {
+ super(properties, EntitySpeedTickerPart.class, EntitySpeedTickerPart::new);
+ }
+
+ @Override
+ public void appendHoverText(ItemStack stack, TooltipContext context, List tooltip, TooltipFlag tooltipFlag) {
+ super.appendHoverText(stack, context, tooltip, tooltipFlag);
+ tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.requirement", "需要放入实体加速卡以启用加速"));
+ tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.max", "最高可达 1024x 加速"));
+ tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.energy", "加速将消耗 AE 网络能量,网络能量不足时无法加速"));
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java
new file mode 100644
index 0000000..e72e8ce
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java
@@ -0,0 +1,151 @@
+package com.extendedae_plus.ae.menu;
+
+import appeng.core.definitions.AEItems;
+import appeng.menu.guisync.GuiSync;
+import appeng.menu.implementations.UpgradeableMenu;
+import appeng.menu.slot.OptionalFakeSlot;
+import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
+import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
+import com.extendedae_plus.config.ModConfigs;
+import com.extendedae_plus.init.ModItems;
+import com.extendedae_plus.init.ModMenuTypes;
+import com.extendedae_plus.util.ConfigParsingUtils;
+import com.extendedae_plus.util.PowerUtils;
+import it.unimi.dsi.fastutil.shorts.ShortSet;
+import net.minecraft.client.Minecraft;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+
+// 实体加速器菜单,负责与客户端界面同步数据
+public class EntitySpeedTickerMenu extends UpgradeableMenu {
+ @GuiSync(716)
+ public boolean accelerateEnabled = true;
+ // 已安装的实体加速卡数量(用于能耗计算)
+ @GuiSync(717)
+ public int entitySpeedCardCount;
+ // 已安装的能量卡数量
+ @GuiSync(718)
+ public int energyCardCount;
+ // 当前生效的配置倍率(从配置中读取并同步)
+ // 当前计算出的生效速度(product of multipliers),同步给客户端用于显示
+ @GuiSync(719)
+ public int effectiveSpeed = 1;
+ @GuiSync(720)
+ public double multiplier = 1.0;
+ @GuiSync(721)
+ public boolean targetBlacklisted = false;
+
+ // 构造方法,初始化菜单并与部件绑定
+ public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) {
+ super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host);
+ // 让部件持有当前菜单实例,便于通信
+ getHost().menu = this;
+ // 初始同步部件上的开关状态到菜单(服务器端构造时保证一致)
+ try {
+ this.accelerateEnabled = getHost().getAccelerateEnabled();
+ } catch (Exception ignored) {
+ }
+ }
+
+ public boolean getAccelerateEnabled() {
+ return this.accelerateEnabled;
+ }
+
+ public void setAccelerateEnabled(boolean enabled) {
+ this.accelerateEnabled = enabled;
+ }
+
+ @Override
+ public void onServerDataSync(ShortSet updatedFields) {
+ super.onServerDataSync(updatedFields);
+ // 重新统计实体加速卡和能量卡数量
+ this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
+ this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
+
+ // 计算当前面向方块的倍率(服务器端),并同步给客户端
+ double mult = 1.0;
+ try {
+ BlockEntity target = getHost().getLevel().getBlockEntity(
+ getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
+ );
+ if (target != null) {
+ String blockId = BuiltInRegistries.BLOCK.getKey(target.getBlockState().getBlock()).toString();
+ for (ConfigParsingUtils.MultiplierEntry me :
+ ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) {
+ if (me.pattern.matcher(blockId).matches()) {
+ mult = Math.max(mult, me.multiplier);
+ }
+ }
+ }
+ } catch (Exception ignored) {}
+ this.multiplier = mult;
+
+ // 检查目标是否在黑名单中,如果是则标记并将生效速度设为 0(服务器端计算)
+ boolean blacklisted = false;
+ try {
+ BlockEntity target = getHost().getLevel().getBlockEntity(
+ getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
+ );
+ if (target != null) {
+ Block block = target.getBlockState().getBlock();
+ String blockId = BuiltInRegistries.BLOCK.getKey(block).toString(); // 直接拿到 "minecraft:stone"
+
+ for (java.util.regex.Pattern p : ConfigParsingUtils.getCachedBlacklist(
+ ModConfigs.ENTITY_TICKER_BLACK_LIST.get())) {
+ if (p.matcher(blockId).matches()) {
+ blacklisted = true;
+ break;
+ }
+ }
+ }
+ } catch (Exception ignored) {
+ }
+ this.targetBlacklisted = blacklisted;
+
+ // 计算生效速度:如果被黑名单则为 0,否则进行正常计算(使用工具类从菜单直接计算 product with cap,最多 8 张)
+ if (this.targetBlacklisted) {
+ this.effectiveSpeed = 0;
+ } else {
+ this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
+ }
+
+ // 如果在客户端,刷新界面
+ if (isClientSide()) {
+ if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
+ screen.refreshGui();
+ }
+ }
+ }
+
+ // 当任意槽位发生变化时调用
+ @Override
+ public void onSlotChange(net.minecraft.world.inventory.Slot slot) {
+ super.onSlotChange(slot);
+ // 客户端重新统计卡数量并刷新界面
+ if (isClientSide()) {
+ this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
+ this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
+ // 立即在客户端计算生效速度以便界面即时反馈(使用与服务端相同的工具方法,最多 8 张卡)
+ this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
+ if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
+ screen.refreshGui();
+ }
+ }
+ }
+
+ @Override
+ public void broadcastChanges() {
+ // 遍历所有槽位,清理未启用但有物品显示的 OptionalFakeSlot
+ for (Object o : this.slots) {
+ if (o instanceof OptionalFakeSlot fs) {
+ if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) {
+ fs.clearStack();
+ }
+ }
+ }
+ // 调用标准的同步方法,通知监听者数据已更新
+ this.standardDetectAndSendChanges();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java b/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java
new file mode 100644
index 0000000..0f6655f
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java
@@ -0,0 +1,309 @@
+package com.extendedae_plus.ae.parts;
+
+import appeng.api.config.Actionable;
+import appeng.api.config.PowerMultiplier;
+import appeng.api.networking.GridFlags;
+import appeng.api.networking.IGridNode;
+import appeng.api.networking.ticking.IGridTickable;
+import appeng.api.networking.ticking.TickRateModulation;
+import appeng.api.networking.ticking.TickingRequest;
+import appeng.api.parts.IPartCollisionHelper;
+import appeng.api.parts.IPartItem;
+import appeng.api.parts.IPartModel;
+import appeng.api.upgrades.IUpgradeableObject;
+import appeng.core.definitions.AEItems;
+import appeng.items.parts.PartModels;
+import appeng.menu.MenuOpener;
+import appeng.menu.locator.MenuLocators;
+import appeng.parts.PartModel;
+import appeng.parts.automation.UpgradeablePart;
+import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
+import com.extendedae_plus.config.ModConfigs;
+import com.extendedae_plus.init.ModItems;
+import com.extendedae_plus.init.ModMenuTypes;
+import com.extendedae_plus.util.ConfigParsingUtils;
+import com.extendedae_plus.util.PowerUtils;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.MenuProvider;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.BlockEntityTicker;
+import net.minecraft.world.level.block.entity.BlockEntityType;
+import net.minecraft.world.phys.Vec3;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * EntitySpeedTickerPart 是一个可升级的 AE2 部件
+ * 该部件可以加速目标方块实体的 tick 速率,消耗 AE 网络能量,并支持加速卡升级
+ * 功能受Crazy AE2 Addons启发
+ */
+public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject {
+ public static final ResourceLocation MODEL_BASE = ResourceLocation.fromNamespaceAndPath(
+ ExtendedAEPlus.MODID, "part/entity_speed_ticker_part");
+ @PartModels
+ public static final PartModel MODELS_OFF;
+ @PartModels
+ public static final PartModel MODELS_ON;
+ @PartModels
+ public static final PartModel MODELS_HAS_CHANNEL;
+
+ static {
+ MODELS_OFF = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off"));
+ MODELS_ON = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on"));
+ MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel"));
+ }
+
+ // 当前打开的菜单实例(如果有)
+ public EntitySpeedTickerMenu menu;
+ // 控制是否启用加速(默认启用)
+ private boolean accelerateEnabled = true;
+
+ /**
+ * 构造函数,初始化部件并设置网络节点属性
+ *
+ * @param partItem 部件物品
+ */
+ public EntitySpeedTickerPart(IPartItem> partItem) {
+ super(partItem);
+ // 设置网络节点属性:需要通道、空闲功耗为1,并注册为 IGridTickable 服务
+ this.getMainNode()
+ .setFlags(GridFlags.REQUIRE_CHANNEL)
+ .setIdlePowerUsage(1)
+ .addService(IGridTickable.class, this);
+ }
+
+ public boolean getAccelerateEnabled() {
+ return this.accelerateEnabled;
+ }
+
+ public void setAccelerateEnabled(boolean accelerateEnabled) {
+ this.accelerateEnabled = accelerateEnabled;
+ }
+
+ /**
+ * 获取当前状态下的静态模型(用于渲染)
+ *
+ * @return 当前状态的模型
+ */
+ public IPartModel getStaticModels() {
+ if (this.isActive() && this.isPowered()) {
+ return MODELS_HAS_CHANNEL;
+ } else if (this.isPowered()) {
+ return MODELS_ON;
+ } else {
+ return MODELS_OFF;
+ }
+ }
+
+
+
+ /**
+ * 当玩家激活部件(右键)时调用,打开自定义菜单
+ *
+ * @param player 玩家
+ * @param pos 点击位置
+ * @return 总是返回 true,表示激活成功
+ */
+ @Override
+ public final boolean onUseWithoutItem(Player player, Vec3 pos) {
+ if (!isClientSide()) {
+ MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this));
+ }
+ return true;
+ }
+
+ /**
+ * 定义部件的碰撞箱(用于物理碰撞和渲染)
+ *
+ * @param bch 碰撞辅助器
+ */
+ @Override
+ public void getBoxes(IPartCollisionHelper bch) {
+ bch.addBox(2, 2, 14, 14, 14, 16);
+ bch.addBox(5, 5, 12, 11, 11, 14);
+ }
+
+ /**
+ * 获取定时请求,决定本部件多久 tick 一次
+ *
+ * @param iGridNode 网络节点
+ * @return TickingRequest 对象
+ */
+ @Override
+ public TickingRequest getTickingRequest(IGridNode iGridNode) {
+ // 每 1 tick 执行一次
+ return new TickingRequest(1, 1, false);
+ }
+
+ /**
+ * 当升级卡数量发生变化时调用,通知菜单更新
+ */
+ @Override
+ public void upgradesChanged() {
+ if (this.menu != null) {
+ // 使用 AE2 风格:当升级发生变化时让菜单广播变化(槽/数据会被同步),客户端会基于槽内容重新计算并刷新界面
+ this.menu.broadcastChanges();
+ }
+ }
+
+ /**
+ * 网络定时回调,每次 tick 时调用
+ *
+ * @param iGridNode 网络节点
+ * @param ticksSinceLastCall 距离上次调用经过的 tick 数
+ * @return TickRateModulation.IDLE 表示继续保持当前 tick 速率
+ */
+ @Override
+ public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) {
+ // 如果部件的加速开关被关闭,则不进行加速(提前返回)
+ if (!this.getAccelerateEnabled()) {
+ return TickRateModulation.IDLE;
+ }
+
+ // 获取目标方块实体(本部件朝向的方块)
+ BlockEntity target = getLevel().getBlockEntity(getBlockEntity().getBlockPos().relative(getSide()));
+ // 仅在目标存在且部件处于激活状态时执行加速
+ if (target != null && isActive()) {
+ ticker(target);
+ }
+ return TickRateModulation.IDLE;
+ }
+
+ /**
+ * 以指定速度对目标方块实体进行 tick 操作
+ *
+ * @param blockEntity 需要被 tick 的方块实体
+ * @param 方块实体类型
+ */
+ private void ticker(@NotNull T blockEntity) {
+ if (this.getGridNode() == null
+ || this.getMainNode() == null
+ || this.getMainNode().getGrid() == null) {
+ return;
+ }
+
+ // 获取方块实体的位置
+ BlockPos pos = blockEntity.getBlockPos();
+ if (blockEntity.getLevel() == null) return;
+
+
+ // 检查黑名单(支持通配符/正则)
+ String blockId = BuiltInRegistries.BLOCK.getKey(blockEntity.getBlockState().getBlock()).toString();
+
+ // 使用工具类的缓存接口(工具类内部负责懒加载/线程安全)
+ List compiledBlacklist = ConfigParsingUtils.getCachedBlacklist(ModConfigs.ENTITY_TICKER_BLACK_LIST.get());
+ for (Pattern p : compiledBlacklist) {
+ if (p.matcher(blockId).matches()) return;
+ }
+
+ // 获取该方块实体的 Ticker
+ @SuppressWarnings("unchecked")
+ BlockEntityTicker blockEntityTicker = this.getLevel()
+ .getBlockState(pos)
+ .getTicker(this.getLevel(), (BlockEntityType) blockEntity.getType());
+ if (blockEntityTicker == null) return;
+
+ // 使用集中定义的 CardDef 列表,支持以后添加等级或改倍率而无需修改此逻辑
+ int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
+
+ // 使用已注册的单一 Item 计算已安装卡数量(总计,用于能耗计算)
+ int entitySpeedCardCount = getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
+
+ // 使用工具方法从槽位直接计算乘积并应用 cap(最多 8 张卡)
+ long product = PowerUtils.computeProductWithCapFromStacks(this.getUpgrades(), 8);
+
+ // 如果没有任何实体加速卡,则不进行加速且不消耗额外能量(只保留部件的被动功耗)
+ if (entitySpeedCardCount <= 0) return;
+
+ // 计算本次 tick 所需能量:使用工具类根据 product 计算最终能耗
+ double requiredPower = PowerUtils.computeFinalPowerForProduct(product, energyCardCount);
+
+ int speed = (int) product;
+
+ double multiplier = 1.0;
+ for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) {
+ if (me.pattern.matcher(blockId).matches()) {
+ multiplier = Math.max(multiplier, me.multiplier);
+ }
+ }
+
+ requiredPower *= multiplier;
+
+ // 先模拟提取以检查网络中是否有足够能量,再真正抽取
+ double simulated = getMainNode().getGrid().getEnergyService()
+ .extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG);
+ if (simulated < requiredPower) return;
+
+ double extractedPower = getMainNode().getGrid().getEnergyService()
+ .extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG);
+ if (extractedPower < requiredPower) return;
+
+ // 计算加速倍数:基于 2 的次方,并把 8 张映射到最大 1024x(2^10)
+ // 已由 product 计算得到 speed;上面已在没有卡时提前返回
+
+ // 执行 tick 操作
+ for (int i = 0; i < speed - 1; i++) {
+ blockEntityTicker.tick(
+ blockEntity.getLevel(),
+ blockEntity.getBlockPos(),
+ blockEntity.getBlockState(),
+ blockEntity
+ );
+ }
+ }
+
+ /**
+ * 判断部件是否有自定义名称
+ *
+ * @return 是否有自定义名称
+ */
+ @Override
+ public boolean hasCustomName() {
+ return super.hasCustomName();
+ }
+
+ /**
+ * 获取部件的显示名称
+ *
+ * @return 显示名称
+ */
+ @Override
+ public @NotNull Component getDisplayName() {
+ return super.getDisplayName();
+ }
+
+ /**
+ * 创建自定义菜单(GUI)
+ *
+ * @param containerId 容器ID
+ * @param playerInventory 玩家背包
+ * @param player 玩家
+ * @return 菜单实例
+ */
+ @Override
+ public @Nullable AbstractContainerMenu createMenu(int containerId,
+ @NotNull Inventory playerInventory,
+ @NotNull Player player) {
+ return new EntitySpeedTickerMenu(containerId, playerInventory, this);
+ }
+
+ /**
+ * 获取可用的升级卡槽数量
+ *
+ * @return 升级卡槽数量
+ */
+ @Override
+ protected int getUpgradeSlots() {
+ return 8;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java b/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java
new file mode 100644
index 0000000..32421e2
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java
@@ -0,0 +1,136 @@
+package com.extendedae_plus.ae.screen;
+
+import appeng.api.config.Settings;
+import appeng.api.config.YesNo;
+import appeng.client.gui.Icon;
+import appeng.client.gui.implementations.UpgradeableScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.CommonButtons;
+import appeng.client.gui.widgets.SettingToggleButton;
+import appeng.util.Platform;
+import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
+import com.extendedae_plus.network.ToggleEntityTickerC2SPacket;
+import com.extendedae_plus.util.PowerUtils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.player.Inventory;
+
+import java.util.List;
+
+public class EntitySpeedTickerScreen extends UpgradeableScreen {
+ private boolean eap$entitySpeedTickerEnabled = false;
+ private SettingToggleButton eap$entitySpeedTickerToggle;
+
+ public EntitySpeedTickerScreen(
+ EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
+ super((C) menu, playerInventory, title, style);
+ this.addToLeftToolbar(CommonButtons.togglePowerUnit());
+
+ try{
+ this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
+ }catch (Exception ignored){}
+
+ // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡”
+ // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
+ eap$entitySpeedTickerToggle = new SettingToggleButton<>(
+ Settings.BLOCKING_MODE,
+ this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO,
+ (btn, backwards) -> {
+ // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
+ var conn = Minecraft.getInstance().getConnection();
+ if (conn != null) conn.send(ToggleEntityTickerC2SPacket.INSTANCE);
+ }
+ ) {
+ @Override
+ public List getTooltipMessage() {
+ // 如果目标在黑名单中,直接显示已禁用的提示
+ try {
+ if (menu != null && menu.targetBlacklisted) {
+ var title = Component.literal("实体加速");
+ var stateLine = Component.literal("已禁用(目标在黑名单)");
+ return List.of(title, stateLine);
+ }
+ } catch (Exception ignored) {}
+
+ boolean enabled = eap$entitySpeedTickerEnabled;
+ var title = Component.literal("实体加速");
+ var stateLine = enabled
+ ? Component.literal("已启用: 将加速目标方块实体的tick")
+ : Component.literal("已关闭: 不会对目标方块实体进行加速");
+ return List.of(title, stateLine);
+ }
+
+ @Override
+ protected Icon getIcon() {
+ try {
+ if (menu != null && menu.targetBlacklisted) {
+ // 黑名单时显示禁用图标
+ return Icon.INVALID;
+ }
+ } catch (Exception ignored) {}
+
+ // 根据当前值显示不同图标(可按需替换 Icon 常量)
+ if (this.getCurrentValue() == YesNo.YES) {
+ return Icon.VALID;
+ } else {
+ return Icon.INVALID;
+ }
+ }
+ };
+ // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
+ eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO);
+
+ this.addToLeftToolbar(eap$entitySpeedTickerToggle);
+ }
+
+ @Override
+ protected void updateBeforeRender() {
+ super.updateBeforeRender();
+
+ if (this.eap$entitySpeedTickerToggle != null) {
+ boolean desired = this.eap$entitySpeedTickerEnabled;
+ if (this.menu != null) {
+ desired = this.menu.getAccelerateEnabled();
+ }
+
+ this.eap$entitySpeedTickerEnabled = desired;
+ // 如果目标在黑名单中,禁用切换并强制显示为关闭
+ if (this.menu != null && this.menu.targetBlacklisted) {
+ this.eap$entitySpeedTickerToggle.set(YesNo.NO);
+ this.eap$entitySpeedTickerToggle.active = false;
+ } else {
+ this.eap$entitySpeedTickerToggle.set(desired ? YesNo.YES : YesNo.NO);
+ this.eap$entitySpeedTickerToggle.active = true;
+ }
+ }
+ textData();
+ }
+
+ public void refreshGui() {
+ textData();
+ }
+
+ private void textData() {
+ // 如果目标被黑名单禁止,则显示禁用状态并把数值显示为 0
+ if (getMenu().targetBlacklisted) {
+ setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable"));
+ setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0));
+ setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false)));
+ setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0)));
+ setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0)));
+ return;
+ }
+
+ int energyCardCount = getMenu().energyCardCount;
+ double multiplier = getMenu().multiplier;
+ int effectiveSpeed = getMenu().effectiveSpeed;
+
+ double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount);
+ double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount);
+
+ 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)));
+ setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier)));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/client/ClientProxy.java b/src/main/java/com/extendedae_plus/client/ClientProxy.java
index 5a7d020..9c373ee 100644
--- a/src/main/java/com/extendedae_plus/client/ClientProxy.java
+++ b/src/main/java/com/extendedae_plus/client/ClientProxy.java
@@ -1,18 +1,21 @@
package com.extendedae_plus.client;
import appeng.client.render.crafting.CraftingCubeModel;
+import appeng.init.client.InitScreens;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
+import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
+import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
-import com.extendedae_plus.client.screen.GlobalProviderModesScreen;
-import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
import com.extendedae_plus.hooks.BuiltInModelHooks;
-import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
-import net.minecraft.client.gui.screens.MenuScreens;
-import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
-import net.neoforged.fml.common.EventBusSubscriber;
+import com.extendedae_plus.init.ModItems;
+import com.extendedae_plus.init.ModMenuTypes;
+import net.minecraft.client.renderer.item.ItemProperties;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
+import net.neoforged.fml.common.EventBusSubscriber;
+import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
/**
* 客户端模型注册,将 formed 模型注册为内置模型。
@@ -26,6 +29,10 @@ public final class ClientProxy {
public static void init() {
if (REGISTERED) return;
REGISTERED = true;
+ // 注册 Item property,用于根据 ItemStack 的 NBT exponent 切换模型
+ ItemProperties.register(ModItems.ENTITY_SPEED_CARD.get(), ExtendedAEPlus.id("mult"),
+ (stack, world, entity, seed) -> (float) EntitySpeedCardItem.readMultiplier(stack));
+
// 注册四种形成态模型为内置模型
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
@@ -46,16 +53,7 @@ public final class ClientProxy {
BuiltInModelHooks.addBuiltInModel(
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
- }
- /**
- * 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。
- */
- public static void onClientSetup(final FMLClientSetupEvent event) {
- event.enqueueWork(() -> {
- // 确保在首次资源加载前完成内置模型注册(REGISTERED 保护避免重复)
- init();
- });
}
@SubscribeEvent
@@ -75,5 +73,10 @@ public final class ClientProxy {
}
}
);
+
+ /**
+ * 注册由 AE2 InitScreens 所需的屏幕资源映射(用于内置 JSON 屏幕注册)
+ */
+ InitScreens.register(event, ModMenuTypes.ENTITY_TICKER_MENU.get(), EntitySpeedTickerScreen::new, "/screens/entity_speed_ticker.json");
}
}
diff --git a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java
index 11426b6..f0704e0 100644
--- a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java
+++ b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java
@@ -1,6 +1,5 @@
package com.extendedae_plus.client.ui;
-import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
diff --git a/src/main/java/com/extendedae_plus/config/ModConfigs.java b/src/main/java/com/extendedae_plus/config/ModConfigs.java
index d74f914..5abe719 100644
--- a/src/main/java/com/extendedae_plus/config/ModConfigs.java
+++ b/src/main/java/com/extendedae_plus/config/ModConfigs.java
@@ -2,6 +2,8 @@ package com.extendedae_plus.config;
import net.neoforged.neoforge.common.ModConfigSpec;
+import java.util.List;
+
public final class ModConfigs {
public static final ModConfigSpec COMMON_SPEC;
public static final ModConfigSpec.IntValue PAGE_MULTIPLIER;
@@ -12,7 +14,9 @@ public final class ModConfigs {
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
public static final ModConfigSpec.IntValue CRAFTING_PAUSE_THRESHOLD;
-
+ public static final ModConfigSpec.IntValue ENTITY_TICKER_COST;
+ public static final ModConfigSpec.ConfigValue> ENTITY_TICKER_BLACK_LIST;
+ public static final ModConfigSpec.ConfigValue> ENTITY_TICKER_MULTIPLIERS;
static {
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
@@ -22,7 +26,8 @@ public final class ModConfigs {
.comment(
"扩展样板供应器总槽位容量的倍率",
"基础为36,每页仍显示36格,倍率会增加总页数/总容量",
- "建议范围 1-16")
+ "建议范围 1-16"
+ )
.defineInRange("pageMultiplier", 1, 1, 64);
// 是否显示样板编码玩家(通用)
@@ -59,7 +64,8 @@ public final class ModConfigs {
"智能倍增时是否对样板供应器轮询分配",
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
"注意:所有相关供应器需开启智能倍增,否则可能失效",
- "默认: true")
+ "默认: true"
+ )
.define("providerRoundRobinEnable", true);
// 智能倍增的最大倍数(以单次样板产出为单位)。
@@ -67,7 +73,8 @@ public final class ModConfigs {
SMART_SCALING_MAX_MULTIPLIER = builder
.comment(
"智能倍增的最大倍数(0 表示不限制)",
- "此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模")
+ "此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模"
+ )
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
builder.pop(); // pop smart
@@ -79,19 +86,55 @@ public final class ModConfigs {
WIRELESS_MAX_RANGE = builder
.comment(
"无线收发器最大连接距离(单位:方块)",
- "从端与主端的直线距离需小于等于该值才会建立连接。")
+ "从端与主端的直线距离需小于等于该值才会建立连接。"
+ )
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
// 是否允许跨维度连接(忽略维度差异进行频道传输)。
WIRELESS_CROSS_DIM_ENABLE = builder
.comment(
"是否允许无线收发器跨维度建立连接",
- "开启后,从端可连接到不同维度的主端(忽略距离限制)")
+ "开启后,从端可连接到不同维度的主端(忽略距离限制)"
+ )
.define("wirelessCrossDimEnable", true);
builder.pop(); // pop wireless
-// builder.pop(); // pop extendedae_plus
+ builder.push("entitySpeedTicker");
+
+ ENTITY_TICKER_COST = builder
+ .comment(
+ "实体加速器能量消耗基础值"
+ )
+ .defineInRange("entityTickerCost", 512, 0 , Integer.MAX_VALUE);
+
+
+ ENTITY_TICKER_BLACK_LIST = builder
+ .comment(
+ "实体加速器黑名单:匹配的方块将不会被加速。支持通配符/正则(例如:minecraft:*)",
+ "格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
+ )
+ .defineListAllowEmpty(
+ List.of("entityTickerBlackList"), // 路径
+ List::of, // 默认值
+ () -> "", // 新元素默认值(空字符串,供配置编辑器使用)
+ obj -> obj instanceof String // 验证每个元素是字符串
+ );
+
+ ENTITY_TICKER_MULTIPLIERS = builder
+ .comment(
+ "额外消耗倍率配置:为某些方块设置额外能量倍率,格式 'modid:blockid multiplier',例如 'minecraft:chest 2x'",
+ "支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
+ )
+ .defineListAllowEmpty(
+ List.of("entityTickerMultipliers"), // 路径
+ List::of, // 默认值
+ () -> "", // 新元素默认值(空字符串,供配置编辑器使用)
+ obj -> obj instanceof String // 验证每个元素是字符串
+ );
+
+ builder.pop();
+
COMMON_SPEC = builder.build();
}
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index f07a83b..f6c2a3c 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -26,8 +26,12 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_64x.get());
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
-
-// output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
+ output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
+ // 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
+ output.accept(ModItems.createEntitySpeedCardStack(2));
+ output.accept(ModItems.createEntitySpeedCardStack(4));
+ output.accept(ModItems.createEntitySpeedCardStack(8));
+ output.accept(ModItems.createEntitySpeedCardStack(16));
})
.build());
}
diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java
index 71a6220..1f2dcc6 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -1,8 +1,14 @@
package com.extendedae_plus.init;
+import appeng.api.parts.IPart;
+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.EntitySpeedTickerPartItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister;
@@ -47,8 +53,34 @@ public final class ModItems {
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
);
-// public static final DeferredItem- INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
-// "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
-// );
+ public static final DeferredItem ENTITY_TICKER_PART_ITEM = ITEMS.register(
+ "entity_speed_ticker",
+ () -> new EntitySpeedTickerPartItem(new Item.Properties())
+ );
+ // AE Upgrade Cards: 实体加速卡(四个等级:x2,x4,x8,x16)
+ // 单一实体加速卡 Item(不同等级由 ItemStack.nbt 存储)
+ public static final DeferredItem ENTITY_SPEED_CARD = ITEMS.register(
+ "entity_speed_card",
+ () -> new EntitySpeedCardItem(new Item.Properties())
+ );
+
+ /**
+ * 为 PartItem 注册 AE2 部件模型。
+ * 在客户端进行模型/几何体注册时调用。
+ */
+ public static void registerPartModels() {
+ PartModels.registerModels(
+ PartModelsHelper.createModels(
+ ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
+ )
+ );
+ }
+
+ /**
+ * 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
+ */
+ public static ItemStack createEntitySpeedCardStack(int multiplier) {
+ return EntitySpeedCardItem.withMultiplier(multiplier);
+ }
}
diff --git a/src/main/java/com/extendedae_plus/init/ModMenuTypes.java b/src/main/java/com/extendedae_plus/init/ModMenuTypes.java
index 960ece9..c42986b 100644
--- a/src/main/java/com/extendedae_plus/init/ModMenuTypes.java
+++ b/src/main/java/com/extendedae_plus/init/ModMenuTypes.java
@@ -1,12 +1,15 @@
package com.extendedae_plus.init;
+import appeng.menu.implementations.MenuTypeBuilder;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
+import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
-import net.minecraft.world.inventory.MenuType;
import net.minecraft.core.registries.Registries;
+import net.minecraft.world.inventory.MenuType;
+import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
-import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
public final class ModMenuTypes {
private ModMenuTypes() {}
@@ -16,5 +19,11 @@ public final class ModMenuTypes {
public static final DeferredHolder, MenuType> NETWORK_PATTERN_CONTROLLER =
MENUS.register("network_pattern_controller",
- () -> IMenuTypeExtension.create((id, inv, buf) -> new NetworkPatternControllerMenu(id, inv, buf)));
+ () -> IMenuTypeExtension.create(NetworkPatternControllerMenu::new));
+
+ public static final DeferredHolder, MenuType> ENTITY_TICKER_MENU =
+ MENUS.register("entity_speed_ticker",
+ () -> MenuTypeBuilder
+ .create(EntitySpeedTickerMenu::new, EntitySpeedTickerPart.class)
+ .build("entity_speed_ticker"));
}
diff --git a/src/main/java/com/extendedae_plus/network/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java
similarity index 94%
rename from src/main/java/com/extendedae_plus/network/ModNetwork.java
rename to src/main/java/com/extendedae_plus/init/ModNetwork.java
index 6a57257..4d618f5 100644
--- a/src/main/java/com/extendedae_plus/network/ModNetwork.java
+++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java
@@ -1,12 +1,14 @@
-package com.extendedae_plus.network;
+package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.network.*;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
public class ModNetwork {
// 在 Mod 构造中通过 modEventBus.addListener(ModNetwork::registerPayloadHandlers) 注册
public static void registerPayloadHandlers(final RegisterPayloadHandlersEvent event) {
var registrar = event.registrar(ExtendedAEPlus.MODID);
+ registrar.playToServer(ToggleEntityTickerC2SPacket.TYPE, ToggleEntityTickerC2SPacket.STREAM_CODEC, ToggleEntityTickerC2SPacket::handle);
registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle);
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);
diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java
new file mode 100644
index 0000000..9a8de67
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java
@@ -0,0 +1,17 @@
+package com.extendedae_plus.init;
+
+import appeng.api.upgrades.Upgrades;
+import appeng.core.definitions.AEItems;
+import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
+
+
+public class UpgradeCards {
+ public UpgradeCards(final FMLCommonSetupEvent event) {
+ event.enqueueWork(() -> {
+ // 现有:把 Entity Ticker 的部件注册为处理 SPEED/ENERGY 卡的宿主
+ 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");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/integration/jei/ExtendedAEJeiPlugin.java b/src/main/java/com/extendedae_plus/integration/jei/ExtendedAEJeiPlugin.java
index 8c249a2..eecb9dd 100644
--- a/src/main/java/com/extendedae_plus/integration/jei/ExtendedAEJeiPlugin.java
+++ b/src/main/java/com/extendedae_plus/integration/jei/ExtendedAEJeiPlugin.java
@@ -1,10 +1,17 @@
package com.extendedae_plus.integration.jei;
import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
+import mezz.jei.api.constants.VanillaTypes;
+import mezz.jei.api.ingredients.subtypes.ISubtypeInterpreter;
+import mezz.jei.api.ingredients.subtypes.UidContext;
+import mezz.jei.api.registration.ISubtypeRegistration;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
@JeiPlugin
public class ExtendedAEJeiPlugin implements IModPlugin {
@@ -19,4 +26,28 @@ public class ExtendedAEJeiPlugin implements IModPlugin {
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
JeiRuntimeProxy.setRuntime(jeiRuntime);
}
+
+
+ @Override
+ public void registerItemSubtypes(ISubtypeRegistration registration) {
+ // Register NBT-based subtype interpreter so JEI treats different multipliers as distinct items
+ registration.registerSubtypeInterpreter(
+ VanillaTypes.ITEM_STACK,
+ com.extendedae_plus.init.ModItems.ENTITY_SPEED_CARD.get(),
+ new ISubtypeInterpreter() {
+ @Override
+ public @NotNull Object getSubtypeData(@NotNull ItemStack ingredient, @NotNull UidContext context) {
+ // 返回你想让 JEI 区分子类型的数据,这里用 multiplier
+ return EntitySpeedCardItem.readMultiplier(ingredient);
+ }
+
+ @Override
+ public @NotNull String getLegacyStringSubtypeInfo(@NotNull ItemStack ingredient, @NotNull UidContext context) {
+ // 返回同样的值给旧接口兼容
+ return String.valueOf(EntitySpeedCardItem.readMultiplier(ingredient));
+ }
+ }
+ );
+
+ }
}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java
index bf5f561..b423345 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java
@@ -17,7 +17,6 @@ import com.extendedae_plus.api.ExPatternPageAccessor;
import com.extendedae_plus.content.ClientPatternHighlightStore;
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
-import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import com.mojang.logging.LogUtils;
diff --git a/src/main/java/com/extendedae_plus/network/ToggleEntityTickerC2SPacket.java b/src/main/java/com/extendedae_plus/network/ToggleEntityTickerC2SPacket.java
new file mode 100644
index 0000000..0407f95
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/network/ToggleEntityTickerC2SPacket.java
@@ -0,0 +1,50 @@
+package com.extendedae_plus.network;
+
+import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
+import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerPlayer;
+import net.neoforged.neoforge.network.handling.IPayloadContext;
+
+/**
+ * C2S: Toggle the accelerateEnabled flag on the EntitySpeedTickerPart bound to the open menu.
+ */
+public class ToggleEntityTickerC2SPacket implements CustomPacketPayload {
+ public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(
+ ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "toggle_entity_ticker"));
+
+ public static final ToggleEntityTickerC2SPacket INSTANCE = new ToggleEntityTickerC2SPacket();
+
+ public static final StreamCodec STREAM_CODEC =
+ StreamCodec.unit(INSTANCE);
+
+ private ToggleEntityTickerC2SPacket() {}
+
+ @Override
+ public CustomPacketPayload.Type extends CustomPacketPayload> type() {
+ return TYPE;
+ }
+
+ public static void handle(final ToggleEntityTickerC2SPacket msg, final IPayloadContext ctx) {
+ ctx.enqueueWork(() -> {
+ if (!(ctx.player() instanceof ServerPlayer player)) return;
+
+ if (!(player.containerMenu instanceof EntitySpeedTickerMenu menu)) return;
+
+ EntitySpeedTickerPart part = menu.getHost();
+ if (part == null) return;
+
+ // 切换部件上的状态,并把新状态同步到菜单字段,随后广播以通知客户端
+ boolean current = part.getAccelerateEnabled();
+ boolean next = !current;
+ part.setAccelerateEnabled(next);
+ // 确保菜单上的字段也被更新,这样 @GuiSync 会把状态发回客户端
+ menu.setAccelerateEnabled(next);
+ menu.broadcastChanges();
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java b/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java
new file mode 100644
index 0000000..c1f2395
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java
@@ -0,0 +1,172 @@
+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;
+/**
+ * 配置解析工具类:用于解析黑名单与倍率配置的字符串
+ */
+public final class ConfigParsingUtils {
+ private ConfigParsingUtils() {}
+
+ public static final class MultiplierEntry {
+ public final Pattern pattern;
+ public final double multiplier;
+
+ public MultiplierEntry(Pattern pattern, double multiplier) {
+ this.pattern = pattern;
+ this.multiplier = multiplier;
+ }
+ }
+
+ /**
+ * 编译用户提供的匹配串。支持简单的 glob 语法('*' 和 '?')以及完整的正则表达式。
+ * 规则:
+ * - 如果字符串包含 ".*" 或其他正则元字符,优先尝试按正则编译(若失败则回退到 glob 转换)。
+ * - 否则若包含 '*' 或 '?',将按 glob 语法转换为正则。
+ * - 否则按字面量匹配处理。
+ */
+ public static Pattern compilePattern(String raw) {
+ if (raw == null) throw new IllegalArgumentException("pattern is null");
+ raw = raw.trim();
+ if (raw.isEmpty()) throw new IllegalArgumentException("pattern is empty");
+
+ // If it looks like regex (contains '.*' or regex metachar), try regex first
+ if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) {
+ try {
+ return Pattern.compile("^" + raw + "$");
+ } catch (PatternSyntaxException ignored) {
+ // fallback to glob below
+ }
+ }
+
+ // If contains glob chars, convert to regex
+ if (raw.contains("*") || raw.contains("?")) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('^');
+ for (char c : raw.toCharArray()) {
+ switch (c) {
+ case '*': sb.append(".*"); break;
+ case '?': sb.append('.'); break;
+ default:
+ // escape regex special chars
+ if (".\\+[]{}()^$|".indexOf(c) >= 0) {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+ }
+ sb.append('$');
+ return Pattern.compile(sb.toString());
+ }
+
+ // Otherwise treat as a literal (match exact)
+ return Pattern.compile("^" + Pattern.quote(raw) + "$");
+ }
+
+ /**
+ * Parse multiplier entries like 'modid:block 2x' into MultiplierEntry objects.
+ * Accepts values with optional trailing 'x' (case-insensitive).
+ */
+ public static MultiplierEntry parseMultiplierEntry(String entry) {
+ if (entry == null) return null;
+ String[] parts = entry.trim().split("\\s+");
+ if (parts.length < 2) return null;
+ String key = parts[0];
+ String val = parts[1].toLowerCase();
+ if (val.endsWith("x")) val = val.substring(0, val.length() - 1);
+ double m;
+ try {
+ m = Double.parseDouble(val);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ try {
+ Pattern p = compilePattern(key);
+ return new MultiplierEntry(p, m);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ public static List compilePatterns(List extends String> raw) {
+ List out = new ArrayList<>();
+ if (raw == null) return out;
+ for (String s : raw) {
+ if (s == null || s.isBlank()) continue;
+ try { out.add(compilePattern(s)); } catch (IllegalArgumentException ignored) {}
+ }
+ return out;
+ }
+
+ public static List parseMultiplierList(List extends String> raw) {
+ List out = new ArrayList<>();
+ if (raw == null) return out;
+ for (String s : raw) {
+ MultiplierEntry me = parseMultiplierEntry(s);
+ if (me != null) out.add(me);
+ }
+ return out;
+ }
+
+ // ------------------ 全局缓存与接口 ------------------
+ private static volatile List cachedBlacklist = null;
+ private static volatile List cachedMultiplierEntries = null;
+ private static final Object CACHE_LOCK = new Object();
+
+ /**
+ * 获取已解析并缓存的黑名单(线程安全、懒加载)。
+ */
+ public static List getCachedBlacklist(List extends String> source) {
+ if (cachedBlacklist == null) {
+ synchronized (CACHE_LOCK) {
+ if (cachedBlacklist == null) {
+ cachedBlacklist = compilePatterns(source);
+ }
+ }
+ } else {
+ synchronized (CACHE_LOCK) {
+ List newCachedBlackList = compilePatterns(source);
+ if (!cachedBlacklist.equals(newCachedBlackList)) {
+ cachedBlacklist = newCachedBlackList;
+ }
+ }
+ }
+ return Collections.unmodifiableList(cachedBlacklist);
+ }
+
+ /**
+ * 获取已解析并缓存的倍率列表(线程安全、懒加载)。
+ */
+ public static List getCachedMultiplierEntries(List extends String> source) {
+ if (cachedMultiplierEntries == null) {
+ synchronized (CACHE_LOCK) {
+ if (cachedMultiplierEntries == null) {
+ cachedMultiplierEntries = parseMultiplierList(source);
+ }
+ }
+ }else {
+ synchronized (CACHE_LOCK) {
+ List newCachedMultiplierEntries = parseMultiplierList(source);
+ if (!cachedMultiplierEntries.equals(newCachedMultiplierEntries)) {
+ cachedMultiplierEntries = newCachedMultiplierEntries;
+ }
+ }
+ }
+ return Collections.unmodifiableList(cachedMultiplierEntries);
+ }
+
+ /**
+ * 清空缓存,下一次获取时将重新从提供的源解析(或调用方可以重新调用 getter)。
+ */
+ public static void reload() {
+ synchronized (CACHE_LOCK) {
+ cachedBlacklist = null;
+ cachedMultiplierEntries = null;
+ }
+ }
+}
+
+
diff --git a/src/main/java/com/extendedae_plus/util/PowerUtils.java b/src/main/java/com/extendedae_plus/util/PowerUtils.java
new file mode 100644
index 0000000..4da3f2c
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/util/PowerUtils.java
@@ -0,0 +1,162 @@
+package com.extendedae_plus.util;
+
+import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
+import com.extendedae_plus.config.ModConfigs;
+
+
+/**
+ * 用于计算实体加速器的能耗与加速倍率的工具类
+ */
+public final class PowerUtils {
+ private PowerUtils() {}
+ // ---- 重构后的 API ----
+ /**
+ * 将 card multipliers(按插槽序)计算乘积并应用 cap 规则(见 capForHighestMultiplier)
+ * @param multipliers iterable of per-card multipliers
+ * @param maxCards 最多计入的卡数
+ * @return 被 cap 约束后的乘积
+ */
+ public static long computeProductWithCap(Iterable multipliers, int maxCards) {
+ long product = 1L;
+ int considered = 0;
+ int highest = 1;
+ for (Integer m : multipliers) {
+ if (m == null) continue;
+ if (considered >= maxCards) break;
+ int mult = m.intValue();
+ if (mult <= 0) mult = 1;
+ product *= mult;
+ highest = Math.max(highest, mult);
+ considered++;
+ }
+ long cap = capForHighestMultiplier(highest);
+ return Math.min(product, cap);
+ }
+
+ /**
+ * 根据最高单卡 multiplier 返回 cap 值
+ */
+ public static long capForHighestMultiplier(int highestMultiplier) {
+ if (highestMultiplier >= 16) return 1024L;
+ if (highestMultiplier >= 8) return 256L;
+ if (highestMultiplier >= 4) return 64L;
+ if (highestMultiplier >= 2) return 8L;
+ return 1L;
+ }
+
+ /**
+ * 从菜单对象读取前 maxCards 个加速卡的 multiplier 并计算 product with cap
+ */
+ public static long computeProductWithCapFromMenu(appeng.menu.implementations.UpgradeableMenu> menu, int maxCards) {
+ java.util.List list = new java.util.ArrayList<>();
+ int considered = 0;
+ for (var stack : menu.getUpgrades()) {
+ if (considered >= maxCards) break;
+ if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
+ int multVal = EntitySpeedCardItem.readMultiplier(stack);
+ int count = Math.min(stack.getCount(), maxCards - considered);
+ for (int i = 0; i < count; i++) {
+ list.add(multVal);
+ considered++;
+ if (considered >= maxCards) break;
+ }
+ }
+ }
+ return computeProductWithCap(list, maxCards);
+ }
+
+ /**
+ * 从一组 ItemStack(升级槽)直接计算 product with cap(最多 maxCards)
+ */
+ public static long computeProductWithCapFromStacks(Iterable stacks, int maxCards) {
+ java.util.List list = new java.util.ArrayList<>();
+ int considered = 0;
+ for (var stack : stacks) {
+ if (considered >= maxCards) break;
+ if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
+ int multVal = EntitySpeedCardItem.readMultiplier(stack);
+ int count = Math.min(stack.getCount(), maxCards - considered);
+ for (int i = 0; i < count; i++) {
+ list.add(multVal);
+ considered++;
+ if (considered >= maxCards) break;
+ }
+ }
+ }
+ return computeProductWithCap(list, maxCards);
+ }
+
+ /**
+ * 计算最终消耗:把 product 转换为等效卡数(log2)并调用 getFinalPower
+ */
+ public static double computeFinalPowerForProduct(long product, int energyCardCount) {
+ if (product <= 1L) return 0.0;
+ double base = ModConfigs.ENTITY_TICKER_COST.getAsInt();
+
+ // 计算以2为底的对数(用于分档与公式)
+ double log2 = Math.log(product) / Math.log(2.0);
+
+ // 分档:product==2 为一档;4..256 为中档;512..1024 为高档
+ double raw;
+ if (product == 2L) {
+ // 轻量档:线性小幅增长
+ raw = base * 4;
+ } else if (product <= 256L) {
+ // 中档:增长放缓(使用 1.5 * log2)
+ raw = base * Math.pow(2.0, 1.5 * log2) * 2;
+ } else {
+ // 高档:增长较快(使用 2.5 * log2)
+ raw = base * Math.pow(2.0, 2.5 * log2);
+ }
+
+ double reduction = getReductionPercent(energyCardCount);
+ return raw * (1.0 - reduction) / 8.0;
+ }
+
+ /* ----------------- legacy helpers (restored) ----------------- */
+ public static double getGrowthFactor(int speedCardCount) {
+ if (speedCardCount <= 0) return 1.0;
+ if (speedCardCount == 1) return 2.0;
+ if (speedCardCount <= 6) return Math.pow(2.0, 2.0 * speedCardCount);
+ return Math.pow(2.0, 3.0 * speedCardCount);
+ }
+
+ public static double getRawPower(int speedCardCount) {
+ double base = ModConfigs.ENTITY_TICKER_COST.getAsInt();
+ return base * getGrowthFactor(speedCardCount);
+ }
+
+ public static double getReductionPercent(int energyCardCount) {
+ if (energyCardCount <= 0) return 0.0;
+ if (energyCardCount == 1) return 0.1;
+ if (energyCardCount >= 8) return 0.5;
+ return 0.5 * (1.0 - Math.pow(0.7, energyCardCount));
+ }
+
+ public static double getFinalPower(int speedCardCount, int energyCardCount) {
+ double raw = getRawPower(speedCardCount);
+ double reduction = getReductionPercent(energyCardCount);
+ return raw * (1.0 - reduction) / 4.0;
+ }
+
+ /**
+ * 返回能源卡减免后剩余的功耗比率(例如 1 张能源卡 -> 0.9)
+ * @param energyCardCount 能源卡数量
+ * @return 剩余功耗比率
+ */
+ public static double getRemainingRatio(int energyCardCount) {
+ return 1.0 - getReductionPercent(energyCardCount);
+ }
+
+ /**
+ * 将剩余功耗比率格式化为百分比字符串(例如 0.9 -> "90%")
+ */
+ public static String formatPercentage(double ratio) {
+ double pct = ratio * 100.0;
+ // 如果为整数则无小数
+ if (Math.abs(pct - Math.round(pct)) < 1e-9) {
+ return String.format("%d%%", Math.round(pct));
+ }
+ return String.format("%.2f%%", pct);
+ }
+}
diff --git a/src/main/resources/assets/ae2/screens/entity_speed_ticker.json b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json
new file mode 100644
index 0000000..e30da1a
--- /dev/null
+++ b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "schema.json",
+ "includes": [
+ "common/common.json",
+ "common/player_inventory.json"
+ ],
+ "background": {
+ "texture": "guis/blank_background.png",
+ "srcRect": [
+ 0,
+ 0,
+ 176,
+ 205
+ ]
+ },
+ "text": {
+ "dialog_title": {
+ "text": {
+ "translate": "item.extendedae_plus.entity_speed_ticker"
+ },
+ "position": {
+ "left": 8,
+ "top": 6
+ }
+ },
+ "enable": {
+ "position": {
+ "top": 20,
+ "left": 88
+ },
+ "align": "CENTER"
+ },
+ "speed": {
+ "position": {
+ "top": 35,
+ "left": 88
+ },
+ "align": "CENTER"
+ },
+ "energy": {
+ "position": {
+ "top": 50,
+ "left": 88
+ },
+ "align": "CENTER"
+ },
+ "power_ratio": {
+ "position": {
+ "top": 65,
+ "left": 88
+ },
+ "align": "CENTER"
+ }
+ ,
+ "multiplier": {
+ "position": {
+ "top": 80,
+ "left": 88
+ },
+ "align": "CENTER"
+ }
+ },
+ "widgets": {
+ "toolbox": {
+ "right": -2,
+ "bottom": 50,
+ "width": 68,
+ "height": 68
+ }
+ },
+ "slots": {
+ "TOOLBOX": {
+ "bottom": 42,
+ "right": -10,
+ "grid": "BREAK_AFTER_3COLS"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/ae2/textures/guis/blank_background.png b/src/main/resources/assets/ae2/textures/guis/blank_background.png
new file mode 100644
index 0000000000000000000000000000000000000000..daf4474cd4be274c4c9b5cdc74492b30c075de09
GIT binary patch
literal 1776
zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xr)7d$|)7e>}peR2rGbfdS
zL1SX=L|c!;4l+mMgO@5zRVouGJP}}h)74eRTeLf2;TF-TQiGjexM!KDXzKMfWgk4)
ze{@xI_vUqc>zWjPFh0_=(A?$G`QMvMH_680-SOSk{kzK>F03-V`lg$CRhs0?6sI%d
zVMh;W|KUiKaCA{kd$saS65qVXujTCa-8)(_?fXvq;}e2RBp(QOKK3!!dBpqZrmy$&
zJ^nIjqMnL&>hnxqG#%-WnI2pBIHoM(!OTSE=`+>&gu9>0O{<;}$}_d1&0^6Csi)kP
z(?!ok&$$}aT>9>0yo64ccI3LtGVVg&9*VqzMp7#lv2hzlnFjf!WY|A^C|~?1v*_fL
zRZSC<8B148kg@+E|L5H`9bf;Qo(EYj{7t7Z3
zjNdAIZ(db$cgT?5vEyCVC%J;1%sqB5Cn;J^n!hYSi}8K>7hl$dCt?yY#nrFhJpRIX
zA~5{4`GJFq0=mK9b{*f%URC}3yU+6d@>3=iZSk4S9s`UOwj^(N7a$D;Kb?2i11Zh|
zkH}&M20djEW~^9hUj`IpFY)wsWq--cFUYIQe=<=RC?r|p8d2h$pPQSSSHj?2l$uzQ
znxasiS(2gP?&%v4-pD7;z`%0S)5S5Q;?~={u|cyFL>eA8+pjJXWs>GhkuQ`N5P#TO
z=zq>bU*^_DzeOj%3wGF@U;DUo*+boh54UvGw>6v&sNni!H}SB6#lM{s#B&2X9Mr=3
z>h#Wj{=C`vT2DHIJky5TZ*8=7Zl!JR?C4ncdJbob1#`iNW(Ec(1`Y-Vh6XAzsAm$~
zMN~Gjhf$+TNlB?K@k7pripQ_7yuK=l;s2VF&F{Gn8@Sxw=6Se#QO72oCEXI`rAS-o1P_}KgZnZ*Z8
z85}@`01JZvGNVD5+29__=by@JciKJ5>a(L
z?`5H(s`{FdkLK)Jm%KMlKk1wSPxq|DTe`m6ul~;6xclzA!{XtquipRtGnjAEy4U+`
z)93?xtugEhd%D$)W~CxA18n6PRYETRebJe=j~P?xkq${G3$f3Z*?oH^DdjX=RUj}
z{=3lcmDs(Q;-XyEdD-XoJ+I7+?gBD@>N>po&uvgwzs_=-h`)9F{iCi9DDjOFG7a2j
z_xk4U>ejz5!@@A*#KGH*%kLc8eSYEXNc~Fg_lm)}4}E7|-j@4m?a`gj3}mAl#SPjY
z-{7iQww+chj{x|;FEI8-<54CU4e*OAo
zTFPuNr<=Q>l375oFj(a1YGVey4_mo`WfzIyk54^AFuxpE$4yr$kXBDuKbLh*2~7a1
ClaMn2
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
index 2fa9a07..1699f08 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -28,6 +28,25 @@
"item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
+
+ "item.extendedae_plus.entity_speed_ticker": "实体加速器",
+ "screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",
+ "screen.extendedae_plus.entity_speed_ticker.energy": "能耗: %s FE/t",
+ "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",
+
+ "item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速",
+ "item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
+ "item.extendedae_plus.entity_speed_ticker.tip.energy": "加速将消耗 AE 网络能量,网络能量不足时无法加速",
+
+ "item.extendedae_plus.entity_speed_card": "实体加速卡",
+ "item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
+ "item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
+ "item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
+ "item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
+ "tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
+ "tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
"item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰",
@@ -62,6 +81,12 @@
"extendedae_plus.configuration.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
"extendedae_plus.configuration.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
+ "extendedae_plus.configuration.entitySpeedTicker": "实体加速器",
+ "extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值",
+ "extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单",
+ "extendedae_plus.configuration.entityTickerMultipliers": "实体加速器额外消耗倍率",
+ "extendedae_pluscraftingPauseThreshold": "AE合成计算暂停检查阈值",
+
"extendedae_plus.configuration.state_on": "开",
"extendedae_plus.configuration.state_off": "关",
"block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card.json
new file mode 100644
index 0000000..b33694f
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card.json
@@ -0,0 +1,14 @@
+{
+ "parent": "item/generated",
+ "overrides": [
+ { "predicate": { "extendedae_plus:mult": 2.0 }, "model": "extendedae_plus:item/entity_speed_card_x2" },
+ { "predicate": { "extendedae_plus:mult": 4.0 }, "model": "extendedae_plus:item/entity_speed_card_x4" },
+ { "predicate": { "extendedae_plus:mult": 8.0 }, "model": "extendedae_plus:item/entity_speed_card_x8" },
+ { "predicate": { "extendedae_plus:mult": 16.0 }, "model": "extendedae_plus:item/entity_speed_card_x16" }
+ ],
+ "textures": {
+ "layer0": "extendedae_plus:item/entity_speed_card_x2"
+ }
+}
+
+
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x16.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x16.json
new file mode 100644
index 0000000..b1cd950
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x16.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": { "layer0": "extendedae_plus:item/entity_speed_card_x16" }
+}
+
+
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x2.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x2.json
new file mode 100644
index 0000000..1107187
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x2.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": { "layer0": "extendedae_plus:item/entity_speed_card_x2" }
+}
+
+
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x4.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x4.json
new file mode 100644
index 0000000..dc0dfcf
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x4.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": { "layer0": "extendedae_plus:item/entity_speed_card_x4" }
+}
+
+
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x8.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x8.json
new file mode 100644
index 0000000..0b75b61
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_card_x8.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": { "layer0": "extendedae_plus:item/entity_speed_card_x8" }
+}
+
+
diff --git a/src/main/resources/assets/extendedae_plus/models/item/entity_speed_ticker.json b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_ticker.json
new file mode 100644
index 0000000..51789c2
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/entity_speed_ticker.json
@@ -0,0 +1,7 @@
+{
+ "parent": "ae2:item/cable_interface",
+ "textures": {
+ "front": "extendedae_plus:part/entity_speed_ticker_font",
+ "back": "extendedae_plus:part/entity_speed_ticker_back"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_has_channel.json b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_has_channel.json
new file mode 100644
index 0000000..22f76cb
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_has_channel.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2:part/interface_has_channel"
+}
diff --git a/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_off.json b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_off.json
new file mode 100644
index 0000000..a31e532
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_off.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2:part/interface_off"
+}
diff --git a/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_on.json b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_on.json
new file mode 100644
index 0000000..9cfa323
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_on.json
@@ -0,0 +1,3 @@
+{
+ "parent": "ae2:part/interface_on"
+}
diff --git a/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_part.json b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_part.json
new file mode 100644
index 0000000..df7a2d4
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/part/entity_speed_ticker_part.json
@@ -0,0 +1,9 @@
+{
+ "parent": "ae2:part/interface_base",
+ "textures": {
+ "front": "extendedae_plus:part/entity_speed_ticker_font",
+ "sides": "extendedae_plus:part/entity_speed_ticker_sides",
+ "back": "extendedae_plus:part/entity_speed_ticker_back",
+ "particle": "extendedae_plus:part/entity_speed_ticker_back"
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x16.png b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..adfcc54a61cacbeaf5785fe622850a3a8549da53
GIT binary patch
literal 2485
zcmb_e3vd%v6kQ9{R>0PlVGx+QE~u?`vv2dy+K8d)Pnr@iS{UW?vip(-l5EIso3>Pb
z1*d>8phcljlmN9TgG{Roey9Z%{7|L92!6)a(kW#`aXO_3hKOcm>Fz!u0x
zC7%4sAbPm6$iY|6?=k-fmxtWUaa`;*qW+*ZOk>{EzixQBW
zX0wyoVi1ug6-)B~s7ZrKWiUDodW|uYR0Fk+0cwV2Y1YKCfCEM{`k`>Rpy1-BT63ep
z@ST||k>vo#FriRL71F50pqpV$CX>RUR?`?kOBH?@meYP|+yH|WNqo=~kUgTGR2ZRC
zES1d^mfD|#FA$^kOVK*viZSIdz_2Qy~vBxinAt~dE3CId%WTup^(ZEErtb;Xhtd`U0?*m2-
zfPSb=6g;kqtDss3P;)@b>A+v0*fRo@;SIq8&$+~)58{M9KIleF!0)EWSQ)u&(JKaV
zV609PQDL)j1%63}ejXKA%@odA#rd31|&QR;{6pMnO%pTCI*Y8gwq&sCH>IYT#s%
z9u3U5ihQZkS!I5|%LS3g9Dha?Sim~avPir(zNDENwq)iBS7U~Q-fDq7XG_HZvuab_<$YLjQKsR8<{~|qF
z7QO`f-3afT4E5iW`FFnTSNZp3u3B0DUowocU-mnUx!Qb@XIJd4xRH?|ykRSsu~!AY
ziM?!)AJ-{}UnPA7WfcSw_g;b3;>cg#R{O~$$CMF=CQR-a({cHrp&^`pCO7@HWjQHN
zc7|(e1}~hQNZ);A*`1cfo#8O}=t=L^l`k}eM_N*Ltoe0_=f2+;J*pZub>7(e4I%ee
zznp(9Ft)zE{iw|?rCr*5Y2WEh^FPUJ%3Qg1{jeilj}`84B)xGgZYd->FZ@I_?tE$D
zG3}hRj{_>eXYwPCa8s_eeb0ey<~yDS!)j|jY&~>(
zLmwe0RedIzOIJ-AH@W6?&(Yp5v&dt8$DLtumVd!LvxGWg(S{eLHnF|8wyffnp2EH}
zCl16VeJ*Y@w2KUS{rA*+YO5Bck6yN-K4asG9gU+_&~p-lO$?&1=3uvS+DXXM6U)+ugmewc4y%JcI)Rz800?%l24-JZ7#cZW49
zJ`ggYKr_-LR7C$!Fnk5&C|c;0_-K3-lBTAnS^0=&4DGi(E;BW!o7>yp?tXrs&*%Ht
z?_7RrN^;N00g(uT^t6~wY498k4>Y1H{Cy$+kFVgVo5ws|M3C;i
qo;%E^
zXGj^=Ni-w46o3^nIfdWlfoKFtNc4LEGnbRlOwP``jo2@>`!JMejo37em9%=2I0tXe
z@p4mgQqq~6xr~9u5=Ws4ei{MBm(wlMek0b#OT)E%o50XEh&0!TjgbY>3~MTy
zBzQU0prCMuB$cSnpwQ_vby}7FWmHKjH3X?7C<>kZd7IjnL?Ii
z#Guvo7+jtpty}EK6U-Rl2Ofe_kaA3IK$Z#OJXv06n>fo5oRf2LZb^h#Dv0$s1W6Db
z!V{>2kN+V6b8EE*H6H85
zp*vNZ3#*m3xJ3!L8O~xdVlZX}&$F}=FeC*?Ev`~!0-OcvOx(aJRXEV-G$hB7YL&{?
zx!)u(S#o9N{q0@O3Jm1d?-P0r1|J{uL$9hLv7zc3MIXF29Ob&*SM@)NBh)3(y_Uxf?)Cq?(
zPF@D5E4&EKXU+|&y>R+AJxCsfAfa^@)0lK~{?{dq6Q2kDzD(Sps{Q0BJ$FPw`OI8p
ze8vzE5u1DU*#1HN#^&`=M__T$Ju0r2)mXBRzutdx9-5zCc|W(Nc){plQC+v}*>;a<
zK6iFjN?z9KsXoo!6JlU?^Oe@mzrXT9ORIU8ODru4=~0hVg)E7lAGw4NjgIJoIGl6F
zo>Z4651KG@uXZqU=5X_uC9ci9dFb)tZbvT0ymp{GA-2AMH&wiUadzRsdzWV+YJ0De
z9ifgmf0{PuNX3B(<*&zFJ|1^-P5#hHw!ZsL=G={Jjlb*K(QCBjRQ865ll{(?zqH5F
zx_bTTlTnqwZJ)fedcvVEMtt_-ZqL-yRPSE!TU`?Q%Tc6kRL#=Z-g}tb>c+-3Kh@=_
zs#eyoI3HbJxp?hpm!r4UPTGDpBSrc|{-!g1ciKFD`rv5R6^lsM^TZ{dC|M!Pgd|)p<
za%6&f_lLU<&*KXhetIo=<#h8Fb$5Km5N_#`^7o1~KNPISiz8JYbwl*!h@lFCd+mPkXbAD?)vFOI>g!$UmnbR-ujqA?E
zAY-=;IF*+;FJ<_}&E=Cn-m}J6nNd)%MPIP1s87-OugBFE9jeolqmgShje9R$eXoA7
zF07thc_?gsfWA9v#t@HkTEr8lx>-&I&S66sP7R2^>{Ael05tJ99ZCeqcl6y+CT}
s`Hcw;#w)c+NqM)*jv*}(Yt{EdYu1(2-2DS^@?Q|kxD?a2v9ojk0;E1Ung9R*
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x2.png.mcmeta b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x2.png.mcmeta
new file mode 100644
index 0000000..c488c5a
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x2.png.mcmeta
@@ -0,0 +1,15 @@
+{
+ "animation": {
+ "interpolate": true,
+ "frames": [
+ {
+ "index": 0,
+ "time": 32
+ },
+ {
+ "index": 1,
+ "time": 16
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab8775fce0e9022cc82cdf2fc5979b44aa293f23
GIT binary patch
literal 2501
zcmb_e4Nwzj8eRnv{G}7XKoyr24?DWqzifWCNf8Mpw1xtuMW;IYW%mmNNH%5{61elf
zcb2o-dgl*dr$2gV)k@pxOpluzE9#wt1LAoooUP741-(19GGf*0%-wwnA!j}NGo8z1
zv)_LEeDC|b@B4i3mxiLk{Hc@DCSe#hwZLvGLH9&-;qh_k_sNEPUFeqJvoBRJY_cYF
zjl-JX(qdTLPIu`Nb%|rKAj%#ckYp#+1wB55#xP5E&0D0)c=o!0BXvImMXG=8z4RB@u#D>bxoll3r!bm<1bDM8Dgo
zx@9jOvIv}VjcO$j)zKI{zNlHR63G+F7!?FQiqX-bn8H9wjN*JX{_3!ENu*#k^gyqw
zAS@Hb`YL2qRx0H0FdcpUEe9yK4oB3-W4U-dQ5TAuSBt`kB;>JZrL@imsS>EjHGUE1
z)gqd6LecnyTt5V=>@St&>aj`{jZwx~gO11NmASo=98jKmXaTeV6K=jWOKDc1Ie}xp1x1>XfC_#PEQx|k_Im&d*zE!3kn(xU
z2|SucAy=-J{m3z5$Awuq972ItQGr*41vV>zV%E9cl3$fGZS5Jlf{})Wkc&nRW8XQvhiQ9tbt)gNv|hq6Awreujfd>8V#f<0we8|
zoMw(Ujj?&b??xL9R6lGr#7aUIMH66v$-t8wVnEWgo+p8xH;{m5(2k*jo-?~bY7PrT
zu)7shy1K~zcoD9OjH=yuSQ7#eLt3{IVyMVaA|li7$K>Njd5)|PR6u0(dx}5mU6EaC
z0Qg}}Im+D+ii`R#@(QT^e(Ed`X$H^+lH;6!lz`qznjwoSz#DlQLR!ypuCdS`p^n7J
z2t4XG{FoSj3iU`@#0uaohp2N>#7__A$LVsE<^Klr$jos?y6$V@@4o8F4>AwJ;#ph1+?qOX-?71qQ*KPW+w;zrZ2Ta$eJHm1G<|v-
z<~Wv|79*TFNqyw_ch03-1mC&&cKS10tEyr&1BvK6a$vZ5UEj49Ch6Ubw&Qo!b?-Hn
zX|Gu}U0HGbFjd?_ab{!g;uXJJasKaRd{)f1Y(`pf5fd-t?mnyEcP4JLHV7o1&->@7{6Ys6E}Wo9g_J52|pH>sNwfg$d
zmEcDs>hxOQ-7N)JAp
zzj*|=UeB0&`0BC2bLZB!Z`_dDo;JP1-(U9O{4_F;o!2z6d%<9e&eOiJx3{;(v7l*U
Z_PB+^yK^ct8p40H3i1kVt&3J}_z(4|b&>!8
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png.mcmeta b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png.mcmeta
new file mode 100644
index 0000000..c488c5a
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x4.png.mcmeta
@@ -0,0 +1,15 @@
+{
+ "animation": {
+ "interpolate": true,
+ "frames": [
+ {
+ "index": 0,
+ "time": 32
+ },
+ {
+ "index": 1,
+ "time": 16
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x8.png b/src/main/resources/assets/extendedae_plus/textures/item/entity_speed_card_x8.png
new file mode 100644
index 0000000000000000000000000000000000000000..dbfdae5b48127a7c29b6c395ce4b413f8542b4f9
GIT binary patch
literal 2476
zcmb_e4Nw&48D1cOe-V?=gc^hE+9aax?ce=wJ&|xIhaNeRiCR<3ce~%g3U|BgF5Der
zLJu62mikjFsH75U@D!I!w3>*JY
z5h_Db#TfESCIs;%d*E1TKko
zVyVeRyOeBLBIQ=8a7k5up-@#S*hC_8E~`87UhC!)MO8b@wZ1RjOx
zrFJ4K5)fbJa^u;u3UQl(AqARdaf{7hv3e|K&N>@sY1Ty1EX6P+W8)c`r>*$Nk3iv6
z(aRS&=8ptJS9YRA*AVzsr1%W~_20D^c6etQYoKm5dMT3h11Fd!n_%kOHX!sL2(o0COsl
z%;}MA6h2#pK$q1*SuPvxlzUV%&YBG-e8y7AFUkQem
zXr(!u2Z*Ed9kNgn>1<^Fu**eRKpg*z>M@zkP-HEnMX<6Y%Nb3i6`BEQGICbnwK66K
zf>E}oRS7LLPpcO*{BWLqQ+#EFspDSso
zb)k{_WnjLIZD>IohQ5wF_`A<|@s@)^D@-J>4gEAq(vKnX;o-_rcyFq-Vu13-mY
z#i(}QX)fxU#A~4P+oiKWpcz1$NsjXXQUpd1>L<&gqFGEdL_Ov>?`Z0GNk?X6coW)f
zOrvJ}A<{?6B9s7sF+@8jMf~t&zMn6LRen90yH?i!FBvMbUk*Ery4!rwXLszasFBfI
zXv2;SW3LKy5qsG{KdO_8UM1nY=esa0{(#ewRhYZ}MAM0dspV;Z&FtVV@9Fr{&i1rR
z%<+knooV5$2NEBvKKvlRrzklqKEWAo&))2;JYDO~6DRF=%shBIZd2v;lo?}El6G}n
z>Fyai_re;D-?R71rI~x1e}6oIJ5~LBUmtgBXw&TD{nlv-*DeS4pZ#|3I~mRLSnTtJ7mxP+dP!(jLwch-&3u}8V(rJ1LUEbz^)8k4+yl#R
zk3A%HVi_5KXgj}d`5^Nvf@|<^OHHaxt(#y3wEaK7>mEOF@x$Hn#tKaRbK>s07kAe*
zA8V}0uj)O}+O=@&>b09+hYf$m3!csnhc^~o
z`-|}Qw$(?6_ISAXvvo%Y@z7eekKZ=t+O;A2(dRNxQq$9jBhRi$F0gJ^I$up5bk8~$
zxG%5w)mMl^;?I-KK>6ZU>QM2DeQz!H{IR+#q4!{zn9x+T@j~JwL&ZN^mr!onp!nww
z%zvTZdq-UjJ)x=d
z*rA?*i&x_Y({FZN9r~BtI_0KowxxOgg8rJ%K3Tl5B|U#p{|(2^F<488x2$=%aia8e
zW$AC4Hhejo{PdN7SFU?4pQ}#)399PU`X9IdZ9;o;;v*}z
zFK<;qN|HOL=ElyC-aXfHvbOtV>Fvi>H%@O#Oj*_aL8m$2{qZNs)RA9&Xq%j3PFm18
zHevOtwC$(5i@5RWGd^Fr?~60eIZtvatsNa(LzAYe4=%ZJy{O82A@lsd`cI`bJ`JQs-@6+cU;X;T#}RZ2sJ%4P#rIy*CaUVOKE
z?5^!|{-70vD5(mmK$TjQK&z5~+%zZ!QN^K{LPhw92r8;XAtezYA#kHgZADO|Hq73a
zFRdM40Ev_CZg=Omzj^P?@4Yv(e{kTQn^xbtI+MxV)ZdpM693)l_xkI^??>OdOvK*}
zmA)NuCUfJ>>9;HMje~0i_%(lcEEy|&+M!`NOIYagY_(hw=uBqwUDXPq6FiYzUi5>U
z{Km<7S@Ky<9@Pu5Q0e94e&6nhZ{IyIOm|OEo5^?eNSmvUASm;MNY!#Fh@EOquJJnJ
zI(@9jQVo(!NPOMFrWmopPuu5C{;@1PQ5vAYR{O$n%&+
zekJk4KuQ^j8%`!US(sY)p0?MoYGSGCS1+6%}5hqC)4ToW=DW1WmZBlQq
zv|-c_SU4STOxNG(1K7VBME7NbpUOE&@i$b
zqzlb$g4QB9WWG1EB9u|b3!^d-%jTC!kt>y;Seva-aQcHdApzz6`J60F&H6rbxJQu7
zT>^AZ)d0qt4v2;kP#HB1o!S
zfkm(dFw?M&7{b_fn{4++zQ}M=YG+NYVuI0y9>*>;0Co*egik}jRf!FVt1@VKo+S*G
zrNw!B!%`R(LMCF4Whq^2N6-sXNt#_b88se`r9v%D)*qTG6dwhpzm4%7D~vA=j|9F5
zu_{ZcTAbSa+ePIP1{k`I^~Uo-zsgHvd0#{=t{Y;skLs5IO8
z=NnfgU#g{~LulOvnrf3Xx$)RvVu3pgWu10!u7x3YP1QTJz&ZxxAYcTZOY{>0S$U
zgmlD3kFN65EF14Q($0?14~qE
zQT8kfP1~T1v5vV6gHRP?0I^2uyAzjE1UR%1)0n0~LObO$AhnpNw_>;NVqn^W&=n>E
zOEWxTiTwuQuv0Em96X5FH~>U!L8xgKa1k;9u}obzHA7Sqty3-wQtTns1)i!>fJ{vl
z@lv^<#?*kiW?|cXKi9SFl-k~|>7gzz1S|A={29LPE7i;@p#7woY8)rgdiHfoPPG
zIFSt$9=H1uMe#@}+mBU|01DjCy-uVxI`Oi;H9@_rs-rOgL?mV$A%A8;Gu~omg
zjhd6$?hOy!`Ry~G8kA->{B&*jr%m5{sqdM`EI~uICb{;>?bxJe*H!M+|9}T_nfL5MruSUW^RN8yLgxIl&J90d_ZRUi
zUBQ@tWZ(HWf3=Vted3*+f8qM&lRtR*z|h{g-yAq|v1|YBm*(Dn`lVU?!r4RLgFo-H
zZ#g!SAG>2s=0~~B#hKaJFa3S>I`-tKhTTW@bietB`CT7+`Ma~TM~@$ym^=2f(~lor
zcyYtLK<;0+=j?0aCw8trxQo8}&xLn>x9iB
z-MzcBrv`!#d}O0WO$fmcVq&7nDTD}#mmn9#XmUyj@#5i&sGl^*2TlA$;NSD;orB%m
zIW=*Tndz?j)$jkP|D)=QbH_fmclV9ECnhHL&K{}F!~c7V?^W-H-<$sVCWpVPJ4cRZ
z6BF0GxA;y@Jofkv0RBVVSj-peM?;ykDqJN(SGuhZpeH5{-QMkRc|zxg&`WWvP6Tl4>KU5F|0Ch8I*kUwE$V-(pZq9Sc*75JZBI;1K)9;A4Wg
zR7K%@?Tkc&nPrdEN;vYi(_WEdrBi^I0XExx3!PJ5{*tzHjj+gZ-rQqR_^Ca7A8V-+%w>xgKD0wO%5
zFpH4yV}X2(8TYm3k(9&rwM4EIAyusJrXq=8x|%zd3zRI|dh1(*3a@OLUsLxw{K57X`P6(6`%F7x)1hW=xj|G6oF#!N~Z
zBdL6Wlwbi0XuyziNkk&4l`YCW95p3C)NNopbf5>>mt_hi6*^j(`u2}wj7-|AIvokB
z4o^AsP@QH~GfI<|!8@I1EPI({t+v&mGnL6`Cu9prbwHa2`k~J}&IyR`K@#*rXke8LzCrE$g>yKVu~ibt}$}5oZT6QYkeym+QiT
z!nP5%8As6c9oQL3kj6eyOfiPJ9GA<*(pOM#;j}Mo*a(Qs^
z;Bat2ga<n2!V@F5)!RqzviyIaK%e^HokxG
zuCM*&^$Wkgd;j$F`_A0=#N=O3zVuA%$@5SA{l^Q>->~bn^VoNP`RGGG+W*t6_Qt1P
zt^DlfL!bQ0GL2KmEoxzM~&}bHkc`A3t~D^j!@6+ahB8l$F-10@t1mQAV>mARYQ?_K%YCO*4%)k_`Q5E~Md+1Xip@!joZ
zcWs|tr$h;+DM=NoMCpej@gdh$LrEnB1rnhqDGf-iQkDK!8k#>)N~r`57EK$e3UBYu
zXQ#E}3m|dQ-R;i2`Mr<%z4vAw>fCVm^^G?-CK8G3JK8f{@PA|cz3x-+`=#ki6#kYL
z+xx;q;?pbQZ$sjVLn}e}jMvi}^=9w2n4eEl$G3U1lrMsGBGG(jsYux%j|7|ddxf<4
zk7Lh^g6E{gO==cri!FS>Yaa`E_t=IWHa5siN4)b6p}AxMK%PfbDCKj7&?=?HGOh*B
z@o-8M$|BKVT3i<^6ne9rLW>`8!AugwFqQ<}OzMWMYqIfGLBf)n!cvM5L`;id3mZc9
zBZ9iXajmXQTU8mnrNx0LDq5-3$jC@?L{9oae~Or<8DmHi0tpn379v_gg>dx@LWYMd
z@QRV=7las*+Wt_K7Qxht5BXvZwh&h11QAP>XfZ{SIQFS5=&+hxaVW@@l{+lOb3D%r
zQ3$f6CJSN8W^3Z}!shce=`d;?28*h`%_|-Dj23ySi--PDzsjivxb-hXeisL8wh%7K+Go7f=|_Tqep(-U6cWOii?CwFI0-e$eClxf$|w&e$fj
zv6d8%0GFKf$Yt&Tz%(E{TgnY0L|COyxw
zTn#%4XAY7ynIK0~97Iiqk*c~T<1#ljo6oGz_-rVqRJ^{DUXITIqqY~8MjY&7&XJ+I
z8X}5fBa>1cnYzr0Vyo14$qd?kfd?5*b8}H+s~kYIvCCx}Ye=>=7s96yWJ}aU)Rr8q
zxvnA0iYUzLZSiw{ko6sixhx9tURwYUrqVdO(jq7=5BA1H%?(usbqT{KMdIJ;eBUDT
zv(sNIco1Sq6k@hmW&PbyxxfHTRb{n0-szQit~cXB)WV7(+T5AG%V*+37U!qc?uEd(
z(Q2jX=G&@IIZr63q(xc91r~FYJ7RU%n`eXT3uOg%WPn4kuE5%gSm?Xa2o3nUeu(fQ
zIgwh(^pFl;*>G7_UCHGpQYEY)*9K;qCZmYkwquyYl&R{@IR0_NB}>7Uif7X8>J6`k
zlnu~AKZpL46902oOpX=CWQ>?&*vPRJ6Pc2!K)Ml^xQsipUE9OCu9%ROJy^C>=t1&T
zxgwFIG`BKU{IACu<`;%}Fo#q{8c^t=JP6aG8~AyF7K=HLl`|_fTyUzLsg{gF88=BrT~f%D7Z~&Z*S1)JIEQ^Z(nf6(>Y3SFD-lw+`BJT=TCkME?b;4<6v=dLSD{SSn#%#td5dAy6M=fM6p3okLUhDU
znITP9;4}c$8p@u*ux@J1ah$rbG>tMzmJyZ}ilFJMurrJxjt$}{j-p`7>c!G9GaRV5
zaN4(Jq?-U}gNevcG?yB1-k==Ui>1TGgTlc95o!XUq8P{~L_^fjRaI9ss3c0gSO#XY
zOC%e)lEe_v6$#=cae$VYhE>IoP5a|mS94ODv%1WO3b|5OW?@-!&J_hHR33tRPyATB
zaluRN&c+MSC
z?%Uh1`So4a-=fWl@rJSY5??&tbc?=w^W+PIU73GQw!1IBe$UkTf#J7adGoC;C;bC_
z?V~SU`|{ooAGpE&-i1|(_22r;wXZ+ddXV@2?CYOQJlz;=d1~!rpTE!Ve&f{W_C|C0
zL^FD+>*$d^-~aRd;}2|E-7vITOzb#QxJ}&o3-5`}XAU=>TG>0eF@L!I_&2`r-d{#n
z9h&SrIFUW|*xAV=gKIjPe)r20D_1OiEvK!RnCxEmV9UTwj|hj~IsKc&!JGC>H2*ex
z{)3?xe}C?lp)Gg+D6=Mz9>4e9cQ5Wf_G<+BndoPk%6e-L_+A+J2t-?v!`-(4MKjL9%M!
zlH?zs%P;xd()WLS=7!}b&X@M4n&c%vJ$m-g3)AOr{r2#)%UU-IuT1};
Date: Sun, 21 Sep 2025 02:00:10 +0800
Subject: [PATCH 09/43] =?UTF-8?q?feat:=20=E5=AE=9E=E4=BD=93=E5=8A=A0?=
=?UTF-8?q?=E9=80=9F=E5=99=A8=E4=BD=BF=E7=94=A81.21=E7=89=88=E6=9C=AC?=
=?UTF-8?q?=E6=9D=90=E8=B4=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ae2/textures/guis/blank_background.png | Bin 1776 -> 2432 bytes
.../part/entity_speed_ticker_back.png | Bin 4001 -> 2236 bytes
.../part/entity_speed_ticker_font.png | Bin 3846 -> 1823 bytes
.../part/entity_speed_ticker_sides.png | Bin 4023 -> 2234 bytes
4 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/src/main/resources/assets/ae2/textures/guis/blank_background.png b/src/main/resources/assets/ae2/textures/guis/blank_background.png
index daf4474cd4be274c4c9b5cdc74492b30c075de09..eee919cb8072bca5be8504f69325215c7e4e9a49 100644
GIT binary patch
literal 2432
zcmeHJeN5D57%u}I=(y1_?d@?~ba9iJEZHAx
zo~G~H{@&;LJ)h~0^0H?V=OxcW5G1j*#8!bIF>o1!#LtC~=as$n@R1;wY)}!za6-Rh
z-pVwjAV}P0!BMGI+E+82vcnm`Y-ioca
z*a^E_1R8{rRt2nWEpu?KjU3Hm1SNr__Dl2UiJR6`-L05p*p(q{qrN{iSsaLl4@BPtz
z9X~!e*8g!@pLFQd+x`3KNMzK^zOW13F)ISd?4)|@=?y1R_nco>7vDd}PsBu|ui#~W
zS>t;y%Sh7x$t~}0o+jdk+uG%A0ex%e?d=WL#1f0Dk@!XbJ-Ifg`Am;#(wDJY|3@k<
LF0=KntlRbnc^Rh)
literal 1776
zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xr)7d$|)7e>}peR2rGbfdS
zL1SX=L|c!;4l+mMgO@5zRVouGJP}}h)74eRTeLf2;TF-TQiGjexM!KDXzKMfWgk4)
ze{@xI_vUqc>zWjPFh0_=(A?$G`QMvMH_680-SOSk{kzK>F03-V`lg$CRhs0?6sI%d
zVMh;W|KUiKaCA{kd$saS65qVXujTCa-8)(_?fXvq;}e2RBp(QOKK3!!dBpqZrmy$&
zJ^nIjqMnL&>hnxqG#%-WnI2pBIHoM(!OTSE=`+>&gu9>0O{<;}$}_d1&0^6Csi)kP
z(?!ok&$$}aT>9>0yo64ccI3LtGVVg&9*VqzMp7#lv2hzlnFjf!WY|A^C|~?1v*_fL
zRZSC<8B148kg@+E|L5H`9bf;Qo(EYj{7t7Z3
zjNdAIZ(db$cgT?5vEyCVC%J;1%sqB5Cn;J^n!hYSi}8K>7hl$dCt?yY#nrFhJpRIX
zA~5{4`GJFq0=mK9b{*f%URC}3yU+6d@>3=iZSk4S9s`UOwj^(N7a$D;Kb?2i11Zh|
zkH}&M20djEW~^9hUj`IpFY)wsWq--cFUYIQe=<=RC?r|p8d2h$pPQSSSHj?2l$uzQ
znxasiS(2gP?&%v4-pD7;z`%0S)5S5Q;?~={u|cyFL>eA8+pjJXWs>GhkuQ`N5P#TO
z=zq>bU*^_DzeOj%3wGF@U;DUo*+boh54UvGw>6v&sNni!H}SB6#lM{s#B&2X9Mr=3
z>h#Wj{=C`vT2DHIJky5TZ*8=7Zl!JR?C4ncdJbob1#`iNW(Ec(1`Y-Vh6XAzsAm$~
zMN~Gjhf$+TNlB?K@k7pripQ_7yuK=l;s2VF&F{Gn8@Sxw=6Se#QO72oCEXI`rAS-o1P_}KgZnZ*Z8
z85}@`01JZvGNVD5+29__=by@JciKJ5>a(L
z?`5H(s`{FdkLK)Jm%KMlKk1wSPxq|DTe`m6ul~;6xclzA!{XtquipRtGnjAEy4U+`
z)93?xtugEhd%D$)W~CxA18n6PRYETRebJe=j~P?xkq${G3$f3Z*?oH^DdjX=RUj}
z{=3lcmDs(Q;-XyEdD-XoJ+I7+?gBD@>N>po&uvgwzs_=-h`)9F{iCi9DDjOFG7a2j
z_xk4U>ejz5!@@A*#KGH*%kLc8eSYEXNc~Fg_lm)}4}E7|-j@4m?a`gj3}mAl#SPjY
z-{7iQww+chj{x|;FEI8-<54CU4e*OAo
zTFPuNr<=Q>l375oFj(a1YGVey4_mo`WfzIyk54^AFuxpE$4yr$kXBDuKbLh*2~7a1
ClaMn2
diff --git a/src/main/resources/assets/extendedae_plus/textures/part/entity_speed_ticker_back.png b/src/main/resources/assets/extendedae_plus/textures/part/entity_speed_ticker_back.png
index d74b5fe1375c96fc3122ac8ff9bd97268ad20d15..504ab1bf531ce48a029af21dd946646aeb74daaf 100644
GIT binary patch
delta 877
zcmZ1|zejL_1ryt}jaF5RljE37CvRcQocxqYelj1Iz+@q2VF6QJ14~^)!w^GrD?<}2
zWAn*L%*xzG23AIvR>r2<1{RYon3XtS{K>t{a+4cbBqpzBmQpb_F-$VDFtE@~O|`Vt
zO)@hv)=e}sHPlT?voJ6+Pccb0vox6el37c|(mXXOImz5Y*WAp+T-VIpBvm)bz`{h=
z)Yvl7)YLpV)ilv;vN?;2N?NLgQIdses;-5pWty&qrHO&Ag}Eijg~As>Eak-(VIKj
zK3l|5;#j@$n@~yqzP(CQ3l_A5guh_7$jN68`zEyGH;ba+x(yx^C)_%w`a_WA{W+&M
zstSifyUn6^v52RXb2-kqo>YD;_VML22JiPjuf10;qI=0?>Z(;Y^0r$%5Aa$z#mObW
ztJ3`0XUk&}(@rmCs_)oy(2`w%sb<6W(#fU5t4w^4S{_gP+*+TwTFAKP{Tv2&_kIDv
zMZ4BHFa7&EQ~%)e_2xDfGunPXZdPXyId1)GSKf^?X~q|$HgEqv;qPnBqaWYxziPXu
zf9rjQjxE;~*7tXx7G|iMGOaM@P-R(_Yg#c|Xy*#I{uQf>(u&=3CvCo5zgliGP)$x~
zQN}Wn!(7?kQ-U%WmWE`{X*tEPe%{)Os{gJPpV!`4#Up%qUfFevKHhCwmA@ZOn|(X>
zz3%4952AuwIuw)?S4$piJa~Nm`k((^*Y;msIFltlL)_D4hj~x41B3qg@Sfz$-0u$Q
zhb-Cp&OpNK;OfuTjB+#2N^WswT$HWiDIjR}-j4CZ@3SfaXLICQ&gR_no}%J;_<)4)
zF^h_M?6O>CJq=B6M*??;IF?-AB3{?}zxVZ5t1ma+Ubk$&HcQ3R;Mj%;9gz>^J$G~8
hu$4blfA*XG2Qxnt+w+Q!PHzSv@O1TaS?83{1OQm8PmBNn
literal 4001
zcmc&%eQX>@6+cU;X;T#}RZ2sJ%4P#rIy*CaUVOKE
z?5^!|{-70vD5(mmK$TjQK&z5~+%zZ!QN^K{LPhw92r8;XAtezYA#kHgZADO|Hq73a
zFRdM40Ev_CZg=Omzj^P?@4Yv(e{kTQn^xbtI+MxV)ZdpM693)l_xkI^??>OdOvK*}
zmA)NuCUfJ>>9;HMje~0i_%(lcEEy|&+M!`NOIYagY_(hw=uBqwUDXPq6FiYzUi5>U
z{Km<7S@Ky<9@Pu5Q0e94e&6nhZ{IyIOm|OEo5^?eNSmvUASm;MNY!#Fh@EOquJJnJ
zI(@9jQVo(!NPOMFrWmopPuu5C{;@1PQ5vAYR{O$n%&+
zekJk4KuQ^j8%`!US(sY)p0?MoYGSGCS1+6%}5hqC)4ToW=DW1WmZBlQq
zv|-c_SU4STOxNG(1K7VBME7NbpUOE&@i$b
zqzlb$g4QB9WWG1EB9u|b3!^d-%jTC!kt>y;Seva-aQcHdApzz6`J60F&H6rbxJQu7
zT>^AZ)d0qt4v2;kP#HB1o!S
zfkm(dFw?M&7{b_fn{4++zQ}M=YG+NYVuI0y9>*>;0Co*egik}jRf!FVt1@VKo+S*G
zrNw!B!%`R(LMCF4Whq^2N6-sXNt#_b88se`r9v%D)*qTG6dwhpzm4%7D~vA=j|9F5
zu_{ZcTAbSa+ePIP1{k`I^~Uo-zsgHvd0#{=t{Y;skLs5IO8
z=NnfgU#g{~LulOvnrf3Xx$)RvVu3pgWu10!u7x3YP1QTJz&ZxxAYcTZOY{>0S$U
zgmlD3kFN65EF14Q($0?14~qE
zQT8kfP1~T1v5vV6gHRP?0I^2uyAzjE1UR%1)0n0~LObO$AhnpNw_>;NVqn^W&=n>E
zOEWxTiTwuQuv0Em96X5FH~>U!L8xgKa1k;9u}obzHA7Sqty3-wQtTns1)i!>fJ{vl
z@lv^<#?*kiW?|cXKi9SFl-k~|>7gzz1S|A={29LPE7i;@p#7woY8)rgdiHfoPPG
zIFSt$9=H1uMe#@}+mBU|01DjCy-uVxI`Oi;H9@_rs-rOgL?mV$A%A8;Gu~omg
zjhd6$?hOy!`Ry~G8kA->{B&*jr%m5{sqdM`EI~uICb{;>?bxJe*H!M+|9}T_nfL5MruSUW^RN8yLgxIl&J90d_ZRUi
zUBQ@tWZ(HWf3=Vted3*+f8qM&lRtR*z|h{g-yAq|v1|YBm*(Dn`lVU?!r4RLgFo-H
zZ#g!SAG>2s=0~~B#hKaJFa3S>I`-tKhTTW@bietB`CT7+`Ma~TM~@$ym^=2f(~lor
zcyYtLK<;0+=j?0aCw8trxQo8}&xLn>x9iBxyg+z5|dXmOQ|F$S|lY}nkDO|rJ9=Rnpm2c
z=~|i^8|#`FS{S66SXf#ZCZ|k!qYW*_=gH+0-B@F*(^nH_6b_K-bb7q|VaFNH@(q(KIwOn#%WfCxx2(~+vJteQCn5>u4srCn(MY5a#@$SJ}5
zPVOqUvciXRR=a(yb6MG*#k*kZv?hiJNB1#aC~te1l`bZB#_z`N%C+hp7t5EOc)i!<
zj#7u~uQ=vS)aUnL-fI#A2H{b>#v_^zS`tNosSaRgDv$+9SVsvIkL*TIN7W_
zU*xU3DXAtH$lJ8Su_WnjIXCl!A2ZKAf4}6~ubk3IaL!Fo`tKKXP0A6DTPd9JRz
SycmHY!QkoY=d#Wzp$PzhEZXP*
literal 3846
zcmc&%Ym6jC9pCl9z;PD@y$cc(n!cRzK}}axS3kLd?Cu=i!p*VS9l~BBfqM0Bdo$B>
z-MzcBrv`!#d}O0WO$fmcVq&7nDTD}#mmn9#XmUyj@#5i&sGl^*2TlA$;NSD;orB%m
zIW=*Tndz?j)$jkP|D)=QbH_fmclV9ECnhHL&K{}F!~c7V?^W-H-<$sVCWpVPJ4cRZ
z6BF0GxA;y@Jofkv0RBVVSj-peM?;ykDqJN(SGuhZpeH5{-QMkRc|zxg&`WWvP6Tl4>KU5F|0Ch8I*kUwE$V-(pZq9Sc*75JZBI;1K)9;A4Wg
zR7K%@?Tkc&nPrdEN;vYi(_WEdrBi^I0XExx3!PJ5{*tzHjj+gZ-rQqR_^Ca7A8V-+%w>xgKD0wO%5
zFpH4yV}X2(8TYm3k(9&rwM4EIAyusJrXq=8x|%zd3zRI|dh1(*3a@OLUsLxw{K57X`P6(6`%F7x)1hW=xj|G6oF#!N~Z
zBdL6Wlwbi0XuyziNkk&4l`YCW95p3C)NNopbf5>>mt_hi6*^j(`u2}wj7-|AIvokB
z4o^AsP@QH~GfI<|!8@I1EPI({t+v&mGnL6`Cu9prbwHa2`k~J}&IyR`K@#*rXke8LzCrE$g>yKVu~ibt}$}5oZT6QYkeym+QiT
z!nP5%8As6c9oQL3kj6eyOfiPJ9GA<*(pOM#;j}Mo*a(Qs^
z;Bat2ga<n2!V@F5)!RqzviyIaK%e^HokxG
zuCM*&^$Wkgd;j$F`_A0=#N=O3zVuA%$@5SA{l^Q>->~bn^VoNP`RGGG+W*t6_Qt1P
zt^DlfL!bQ0GL2KmEoxzM~&}bHkc`A3t~D^j!t{a+4cbBqpzBmQqPcF-tQ{Of}UtOtehV
zO-wW~*G)7@HPbaQH@2`evoK6avoxLjl37b7)x_A+!Z0~m*C^RMQ8(G##7H+W&D2EK
zJT=YO$jHzlEh)`(vN?;2N?NLgQIdses;-5pWty&qrHO&Ag}Eijg~ROI;)L6pJ(?15;D;
zM2pD~jmBnHrUsKAat2LK<1{1Avj;dO5kd8kGjH-eUfcQ)LUIud42-8dT^vI!dUGe+
zXWw=ZIZ&@|BeC$bQhiHZbC(Y%2TyED|Iw_^yt_(YMD0rV-BrRDdLkqxV|vrVSe{o|
zHcyyal5}@HzOdxA!u^|Fza92if4TSmUi?K9-s+9-5`4TR`uxwGcVXyZShvpZ&v}Ns
zS6tFRc9s>Dm!CaRzt?8|eiuzw5gvlhRa8{dxQs2g8)0kWklI
z%kJE%Ym|MHbylUXs`hu+{rz=8p`xCXDmJjY1iH*x#-aG*<6r62NFf%+Yu}d6Je%gB
z>3O+CATq&++aXXSCdMz&i+7G}!p@jQQyx7k@(L7rvFsh=xo@8Ao)&Rmq(wmP
zQBJHOb8?jb?W?z>qq7B_1XvC->{z|^y}$ttp5yb(I+ghgcD>&9Z)vU9WQ$kXWn#%b
z)l!YcWqt47y(*M(l<4D>d0Y6A!C_izNvwItk=oBcWe(hyy8I33-6xgj$~L=sz2uF(
zcP(m@jmdKI;Vst0LT|nL;wH)
literal 4023
zcmc&%eQX>@6+ahB8l$F-10@t1mQAV>mARYQ?_K%YCO*4%)k_`Q5E~Md+1Xip@!joZ
zcWs|tr$h;+DM=NoMCpej@gdh$LrEnB1rnhqDGf-iQkDK!8k#>)N~r`57EK$e3UBYu
zXQ#E}3m|dQ-R;i2`Mr<%z4vAw>fCVm^^G?-CK8G3JK8f{@PA|cz3x-+`=#ki6#kYL
z+xx;q;?pbQZ$sjVLn}e}jMvi}^=9w2n4eEl$G3U1lrMsGBGG(jsYux%j|7|ddxf<4
zk7Lh^g6E{gO==cri!FS>Yaa`E_t=IWHa5siN4)b6p}AxMK%PfbDCKj7&?=?HGOh*B
z@o-8M$|BKVT3i<^6ne9rLW>`8!AugwFqQ<}OzMWMYqIfGLBf)n!cvM5L`;id3mZc9
zBZ9iXajmXQTU8mnrNx0LDq5-3$jC@?L{9oae~Or<8DmHi0tpn379v_gg>dx@LWYMd
z@QRV=7las*+Wt_K7Qxht5BXvZwh&h11QAP>XfZ{SIQFS5=&+hxaVW@@l{+lOb3D%r
zQ3$f6CJSN8W^3Z}!shce=`d;?28*h`%_|-Dj23ySi--PDzsjivxb-hXeisL8wh%7K+Go7f=|_Tqep(-U6cWOii?CwFI0-e$eClxf$|w&e$fj
zv6d8%0GFKf$Yt&Tz%(E{TgnY0L|COyxw
zTn#%4XAY7ynIK0~97Iiqk*c~T<1#ljo6oGz_-rVqRJ^{DUXITIqqY~8MjY&7&XJ+I
z8X}5fBa>1cnYzr0Vyo14$qd?kfd?5*b8}H+s~kYIvCCx}Ye=>=7s96yWJ}aU)Rr8q
zxvnA0iYUzLZSiw{ko6sixhx9tURwYUrqVdO(jq7=5BA1H%?(usbqT{KMdIJ;eBUDT
zv(sNIco1Sq6k@hmW&PbyxxfHTRb{n0-szQit~cXB)WV7(+T5AG%V*+37U!qc?uEd(
z(Q2jX=G&@IIZr63q(xc91r~FYJ7RU%n`eXT3uOg%WPn4kuE5%gSm?Xa2o3nUeu(fQ
zIgwh(^pFl;*>G7_UCHGpQYEY)*9K;qCZmYkwquyYl&R{@IR0_NB}>7Uif7X8>J6`k
zlnu~AKZpL46902oOpX=CWQ>?&*vPRJ6Pc2!K)Ml^xQsipUE9OCu9%ROJy^C>=t1&T
zxgwFIG`BKU{IACu<`;%}Fo#q{8c^t=JP6aG8~AyF7K=HLl`|_fTyUzLsg{gF88=BrT~f%D7Z~&Z*S1)JIEQ^Z(nf6(>Y3SFD-lw+`BJT=TCkME?b;4<6v=dLSD{SSn#%#td5dAy6M=fM6p3okLUhDU
znITP9;4}c$8p@u*ux@J1ah$rbG>tMzmJyZ}ilFJMurrJxjt$}{j-p`7>c!G9GaRV5
zaN4(Jq?-U}gNevcG?yB1-k==Ui>1TGgTlc95o!XUq8P{~L_^fjRaI9ss3c0gSO#XY
zOC%e)lEe_v6$#=cae$VYhE>IoP5a|mS94ODv%1WO3b|5OW?@-!&J_hHR33tRPyATB
zaluRN&c+MSC
z?%Uh1`So4a-=fWl@rJSY5??&tbc?=w^W+PIU73GQw!1IBe$UkTf#J7adGoC;C;bC_
z?V~SU`|{ooAGpE&-i1|(_22r;wXZ+ddXV@2?CYOQJlz;=d1~!rpTE!Ve&f{W_C|C0
zL^FD+>*$d^-~aRd;}2|E-7vITOzb#QxJ}&o3-5`}XAU=>TG>0eF@L!I_&2`r-d{#n
z9h&SrIFUW|*xAV=gKIjPe)r20D_1OiEvK!RnCxEmV9UTwj|hj~IsKc&!JGC>H2*ex
z{)3?xe}C?lp)Gg+D6=Mz9>4e9cQ5Wf_G<+BndoPk%6e-L_+A+J2t-?v!`-(4MKjL9%M!
zlH?zs%P;xd()WLS=7!}b&X@M4n&c%vJ$m-g3)AOr{r2#)%UU-IuT1};
Date: Sun, 21 Sep 2025 13:44:32 +0800
Subject: [PATCH 10/43] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=9E?=
=?UTF-8?q?=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8=E3=80=81=E5=AE=9E=E4=BD=93?=
=?UTF-8?q?=E5=8A=A0=E9=80=9F=E5=8D=A1=E9=85=8D=E6=96=B9=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../upgrades/EntitySpeedCardItem.java | 34 +++++++++---------
.../extendedae_plus/init/ModCreativeTabs.java | 14 ++++----
.../com/extendedae_plus/init/ModItems.java | 17 +--------
.../recipe/entity_speed_card_x16.json | 22 +++++++-----
.../recipe/entity_speed_card_x2.json | 11 +++---
.../recipe/entity_speed_card_x4.json | 36 +++++++++----------
.../recipe/entity_speed_card_x8.json | 24 +++++++------
.../recipe/entity_speed_ticker.json | 17 +++++----
.../recipe/infinity_biginteger_cell.json | 18 ----------
9 files changed, 85 insertions(+), 108 deletions(-)
delete mode 100644 src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json
diff --git a/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java b/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
index fe253ae..190aabd 100644
--- a/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
+++ b/src/main/java/com/extendedae_plus/ae/definitions/upgrades/EntitySpeedCardItem.java
@@ -25,30 +25,30 @@ public class EntitySpeedCardItem extends UpgradeCardItem {
super(props);
}
- public static ItemStack withMultiplier(int multiplier) {
+ public static ItemStack withMultiplier(byte multiplier) {
ItemStack stack = new ItemStack(ModItems.ENTITY_SPEED_CARD.get());
CompoundTag tag = new CompoundTag();
- tag.putInt(NBT_MULTIPLIER, multiplier);
+ tag.putByte(NBT_MULTIPLIER, multiplier);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
return stack;
}
- public static int readMultiplier(ItemStack stack) {
- if (stack == null || stack.isEmpty()) return 1;
+ public static byte readMultiplier(ItemStack stack) {
+ if (stack == null || stack.isEmpty()) return (byte) 1;
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
- if (customData == null || !customData.copyTag().contains(NBT_MULTIPLIER)) return 1;
- return customData.copyTag().getInt(NBT_MULTIPLIER);
+ if (customData == null || !customData.copyTag().contains(NBT_MULTIPLIER)) return (byte)1;
+ return customData.copyTag().getByte(NBT_MULTIPLIER);
}
@Override
public @NotNull Component getName(@NotNull ItemStack stack) {
- int mult = readMultiplier(stack);
+ byte mult = readMultiplier(stack);
String key;
switch (mult) {
- case 2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
- case 4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
- case 8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
- case 16 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x16";
+ case (byte)2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
+ case (byte)4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
+ case (byte)8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
+ case (byte)16 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x16";
default -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x1";
}
return Component.translatable(key);
@@ -56,13 +56,13 @@ public class EntitySpeedCardItem extends UpgradeCardItem {
public List getTooltipLines(ItemStack stack) {
- int mult = readMultiplier(stack);
- long cap = 1L;
+ byte mult = readMultiplier(stack);
+ int cap = 1;
switch (mult) {
- case 16 -> cap = 1024L;
- case 8 -> cap = 256L;
- case 4 -> cap = 64L;
- case 2 -> cap = 8L;
+ case (byte)16 -> cap = 1024;
+ case (byte)8 -> cap = 256;
+ case (byte)4 -> cap = 64;
+ case (byte)2 -> cap = 8;
}
MutableComponent line1 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.multiplier", "x" + mult);
MutableComponent line2 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.max", cap);
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index f6c2a3c..10d9144 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -8,11 +8,8 @@ import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
public final class ModCreativeTabs {
- private ModCreativeTabs() {}
-
public static final DeferredRegister TABS =
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExtendedAEPlus.MODID);
-
public static final DeferredHolder MAIN = TABS.register("main",
() -> CreativeModeTab.builder()
.title(Component.translatable("itemGroup." + ExtendedAEPlus.MODID + ".main"))
@@ -28,10 +25,13 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_1024x.get());
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
// 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
- output.accept(ModItems.createEntitySpeedCardStack(2));
- output.accept(ModItems.createEntitySpeedCardStack(4));
- output.accept(ModItems.createEntitySpeedCardStack(8));
- output.accept(ModItems.createEntitySpeedCardStack(16));
+ output.accept(ModItems.createEntitySpeedCardStack((byte) 2));
+ output.accept(ModItems.createEntitySpeedCardStack((byte) 4));
+ output.accept(ModItems.createEntitySpeedCardStack((byte) 8));
+ output.accept(ModItems.createEntitySpeedCardStack((byte) 16));
})
.build());
+
+ private ModCreativeTabs() {
+ }
}
diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java
index 1f2dcc6..e985c1e 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -1,8 +1,5 @@
package com.extendedae_plus.init;
-import appeng.api.parts.IPart;
-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.EntitySpeedTickerPartItem;
@@ -65,22 +62,10 @@ public final class ModItems {
() -> new EntitySpeedCardItem(new Item.Properties())
);
- /**
- * 为 PartItem 注册 AE2 部件模型。
- * 在客户端进行模型/几何体注册时调用。
- */
- public static void registerPartModels() {
- PartModels.registerModels(
- PartModelsHelper.createModels(
- ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
- )
- );
- }
-
/**
* 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
*/
- public static ItemStack createEntitySpeedCardStack(int multiplier) {
+ public static ItemStack createEntitySpeedCardStack(byte multiplier) {
return EntitySpeedCardItem.withMultiplier(multiplier);
}
}
diff --git a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x16.json b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x16.json
index 821378b..838b694 100644
--- a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x16.json
+++ b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x16.json
@@ -7,11 +7,14 @@
],
"key": {
"S": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
- "nbt": {
- "EAS:mult": 8
- }
+ "type": "neoforge:components",
+ "items": "extendedae_plus:entity_speed_card",
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 8
+ }
+ },
+ "strict": true
},
"A": {
"item": "minecraft:nether_star"
@@ -27,11 +30,12 @@
}
},
"result": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
+ "id": "extendedae_plus:entity_speed_card",
"count": 1,
- "nbt": {
- "EAS:mult": 16
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 16
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x2.json b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x2.json
index 6c4466a..2d59638 100644
--- a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x2.json
+++ b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x2.json
@@ -20,11 +20,12 @@
}
},
"result": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
+ "id": "extendedae_plus:entity_speed_card",
"count": 1,
- "nbt": {
- "EAS:mult": 2
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 2
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x4.json b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x4.json
index 91defa0..fc759ed 100644
--- a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x4.json
+++ b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x4.json
@@ -7,28 +7,26 @@
],
"key": {
"S": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
- "nbt": {
- "EAS:mult": 2
- }
+ "type": "neoforge:components",
+ "items": "extendedae_plus:entity_speed_card",
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 2
+ }
+ },
+ "strict": true
},
- "B": {
- "item": "extendedae_plus:256x_crafting_accelerator"
- },
- "Q": {
- "item": "ae2:spatial_cell_component_16"
- },
- "X": {
- "item": "ae2:dense_energy_cell"
- }
+ "B": { "item": "extendedae_plus:256x_crafting_accelerator" },
+ "Q": { "item": "ae2:spatial_cell_component_2" },
+ "X": { "item": "ae2:dense_energy_cell" }
},
"result": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
+ "id": "extendedae_plus:entity_speed_card",
"count": 1,
- "nbt": {
- "EAS:mult": 4
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 4
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x8.json b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x8.json
index 73384f0..9aa57b3 100644
--- a/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x8.json
+++ b/src/main/resources/data/extendedae_plus/recipe/entity_speed_card_x8.json
@@ -7,28 +7,32 @@
],
"key": {
"S": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
- "nbt": {
- "EAS:mult": 4
- }
+ "type": "neoforge:components",
+ "items": "extendedae_plus:entity_speed_card",
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 4
+ }
+ },
+ "strict": true
},
"B": {
"item": "extendedae_plus:1024x_crafting_accelerator"
},
"Q": {
- "item": "ae2:spatial_cell_component_128"
+ "item": "ae2:spatial_cell_component_16"
},
"X": {
"item": "minecraft:nether_star"
}
},
"result": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
+ "id": "extendedae_plus:entity_speed_card",
"count": 1,
- "nbt": {
- "EAS:mult": 8
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 8
+ }
}
}
}
\ No newline at end of file
diff --git a/src/main/resources/data/extendedae_plus/recipe/entity_speed_ticker.json b/src/main/resources/data/extendedae_plus/recipe/entity_speed_ticker.json
index 076d943..4c5e8ef 100644
--- a/src/main/resources/data/extendedae_plus/recipe/entity_speed_ticker.json
+++ b/src/main/resources/data/extendedae_plus/recipe/entity_speed_ticker.json
@@ -7,11 +7,14 @@
],
"key": {
"S": {
- "type": "forge:partial_nbt",
- "item": "extendedae_plus:entity_speed_card",
- "nbt": {
- "EAS:mult": 2
- }
+ "type": "neoforge:components",
+ "items": "extendedae_plus:entity_speed_card",
+ "components": {
+ "minecraft:custom_data": {
+ "EAS:mult": 2
+ }
+ },
+ "strict": true
},
"Z": {
"item": "ae2:dense_energy_cell"
@@ -23,11 +26,11 @@
"item": "minecraft:nether_star"
},
"I": {
- "item": "expatternprovider:ex_io_port"
+ "item": "extendedae:ex_io_port"
}
},
"result": {
- "item": "extendedae_plus:entity_speed_ticker",
+ "id": "extendedae_plus:entity_speed_ticker",
"count": 1
}
}
\ No newline at end of file
diff --git a/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json b/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json
deleted file mode 100644
index 1949a53..0000000
--- a/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "type": "minecraft:crafting_shapeless",
- "ingredients": [
- { "item": "minecraft:turtle_helmet" },
- { "item": "minecraft:dragon_head" },
- { "item": "minecraft:totem_of_undying" },
- { "item": "minecraft:echo_shard" },
- { "item": "ae2:cell_component_256k" },
- { "item": "minecraft:heart_of_the_sea" },
- { "item": "minecraft:nether_star" },
- { "item": "minecraft:netherite_block" },
- { "item": "minecraft:enchanted_golden_apple" }
- ],
- "result": {
- "id": "extendedae_plus:infinity_biginteger_cell",
- "count": 1
- }
-}
\ No newline at end of file
From 47c56a91a4824cf057cf2102185da09690cbe3e9 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sun, 21 Sep 2025 14:46:13 +0800
Subject: [PATCH 11/43] =?UTF-8?q?feat:=20=E6=8C=89zh=5Fcn.json=E9=A1=BA?=
=?UTF-8?q?=E5=BA=8F=E8=A1=A5=E5=85=A8=E5=B9=B6=E6=95=B4=E7=90=86en=5Fus.j?=
=?UTF-8?q?son=E6=96=87=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../assets/extendedae_plus/lang/en_us.json | 110 ++++++++++++------
.../assets/extendedae_plus/lang/zh_cn.json | 66 +++++------
2 files changed, 108 insertions(+), 68 deletions(-)
diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json
index 4072712..9a16f0c 100644
--- a/src/main/resources/assets/extendedae_plus/lang/en_us.json
+++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json
@@ -1,51 +1,95 @@
{
- "gui.expatternprovider.toggle_slots": "Toggle Slots",
+ "gui.expatternprovider.toggle_slots": "Toggle Slot Display",
"gui.expatternprovider.hide_slots": "Hide Slots",
"gui.expatternprovider.show_slots": "Show Slots",
"gui.expatternprovider.clear_selection": "Clear Selection",
- "gui.extendedae_plus.pattern_provider.select": "Select Pattern Provider",
- "gui.extendedae_plus.pattern_provider.selected": "Pattern Provider Selected",
- "gui.extendedae_plus.pattern_provider.unselected": "Click to select this pattern provider",
- "message.extendedae_plus.no_provider_selected": "Please select a pattern provider first",
- "message.extendedae_plus.invalid_pattern": "Please hold a valid encoded pattern",
- "message.extendedae_plus.upload_success": "Pattern uploaded successfully",
- "message.extendedae_plus.upload_failed": "Failed to upload pattern - no empty slots",
- "message.extendedae_plus.uploading_pattern": "Uploading pattern...",
- "gui.extendedae_plus.group_header.choice": "Toggle Group Choice",
- "gui.extendedae_plus.group_header.choiceable": "Group is choiceable",
- "gui.extendedae_plus.group_header.not_choiceable": "Group is not choiceable",
- "itemGroup.extendedae_plus.main": "ExtendedAE Plus",
+ "gui.extendedae_plus.global.toggle_blocking": "Toggle Blocking Mode",
+ "gui.extendedae_plus.global.toggle_adv_blocking": "Toggle Advanced Blocking",
+ "gui.extendedae_plus.global.toggle_smart_doubling": "Toggle Smart Doubling",
+ "gui.extendedae_plus.global.all_on": "All On",
+ "gui.extendedae_plus.global.all_off": "All Off",
- "config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Info",
+ "itemGroup.extendedae_plus.main": "ExtendedAE Plus",
+ "item.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
+ "item.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
+ "item.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
+ "item.extendedae_plus.64x_crafting_accelerator": "64x Crafting Accelerator",
+ "item.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
+ "item.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
+ "item.extendedae_plus.network_pattern_controller": "Pattern Provider Status Controller",
+ "item.extendedae_plus.entity_speed_card": "Entity Speed Card",
+ "item.extendedae_plus.entity_speed_card.x2": "Entity Speed Card (x2)",
+ "item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
+ "item.extendedae_plus.entity_speed_card.x8": "Entity Speed Card (x8)",
+ "item.extendedae_plus.entity_speed_card.x16": "Entity Speed Card (x16)",
+ "tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s",
+ "tooltip.extendedae_plus.entity_speed_card.max": "Maximum Effect: %s Times",
+
+ "block.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
+ "block.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
+ "block.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
+ "block.extendedae_plus.64x_crafting_accelerator": "64x Crafting Accelerator",
+ "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 Provider Status Controller",
+
+ "extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix",
+ "extendedae_plus.upload_to_matrix.success": "Pattern Uploaded to Assembly Matrix",
+ "extendedae_plus.upload_to_matrix.fail_not_crafting": "Only Crafting Patterns Can Be Uploaded, Processing Patterns Will Be Ignored",
+ "extendedae_plus.upload_to_matrix.fail_no_matrix": "No Formed Assembly Matrix Found in the Current Network",
+ "extendedae_plus.upload_to_matrix.fail_full": "Assembly Matrix Pattern Storage Is Full or Cannot Be Inserted",
+ "extendedae_plus.screen.reload_mapping": "Reload Mapping",
+ "extendedae_plus.screen.add_mapping": "Add Mapping",
+ "extendedae_plus.screen.cn_name": "Chinese Name",
+ "extendedae_plus.button.choose_provider": "Upload Pattern",
+ "extendedae_plus.pattern.hovertext.player": "Encoded by %s",
+
+ "item.extendedae_plus.entity_speed_ticker": "Entity Accelerator",
+ "screen.extendedae_plus.entity_speed_ticker.enable": "§c§lMachine Has Been Disabled",
+ "screen.extendedae_plus.entity_speed_ticker.energy": "Energy Consumption: %s FE/t",
+ "screen.extendedae_plus.entity_speed_ticker.power_ratio": "Power Ratio: %s",
+ "screen.extendedae_plus.entity_speed_ticker.speed": "Current Acceleration Multiplier: %d",
+ "screen.extendedae_plus.entity_speed_ticker.multiplier": "Additional Consumption Multiplier: %s",
+
+ "item.extendedae_plus.entity_speed_ticker.tip.requirement": "Requires Entity Speed Card to Enable Acceleration",
+ "item.extendedae_plus.entity_speed_ticker.tip.max": "Up to 1024x Acceleration",
+ "item.extendedae_plus.entity_speed_ticker.tip.energy": "Acceleration Consumes AE Network Energy; Insufficient Network Energy Prevents Acceleration",
+
+ "config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Information",
"config.jade.plugin_extendedae_plus.wt_frequency": "Show Frequency",
"config.jade.plugin_extendedae_plus.wt_master_mode": "Show Master/Slave Mode",
- "config.jade.plugin_extendedae_plus.wt_master_location": "Show Master Position",
- "config.jade.plugin_extendedae_plus.wt_locked": "Show Locked State",
- "config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State",
+ "config.jade.plugin_extendedae_plus.wt_master_location": "Show Master Node Location",
+ "config.jade.plugin_extendedae_plus.wt_locked": "Show Lock Status",
+ "config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online Status",
"extendedae_plus.tooltip.frequency": "Frequency: %d",
"extendedae_plus.tooltip.master_mode": "Mode: %s",
- "extendedae_plus.tooltip.locked": "Locked: %s",
- "item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence",
- "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6Through ninefold sacrifice, the Void echoes§r—§8Iava, Lord of the Void§r, bestows upon thee this artifact",
- "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b—§4A §dUni§cverse §eWith§ain §6A §bSin§5gle §9Point",
+ "extendedae_plus.tooltip.locked": "Lock Status: %s",
- "screen.extendedae_plus.title": "ExtendedAE Plus Config",
+ "screen.extendedae_plus.title": "ExtendedAE Plus Configuration",
"config.extendedae_plus": "ExtendedAE Plus",
+
"extendedae_plus.configuration.general": "General",
- "extendedae_plus.configuration.pageMultiplier": "Pattern Provider Page Multiplier",
- "extendedae_plus.configuration.pageMultiplier_with_range": "Pattern Provider Page Multiplier (1-64)",
- "extendedae_plus.configuration.showEncoderPatternPlayer": "Show Encoder Player",
- "extendedae_plus.configuration.providerRoundRobinEnable": "Enable Provider Round-Robin",
+ "extendedae_plus.configuration.pageMultiplier": "Pattern Provider Slot Multiplier",
+ "extendedae_plus.configuration.pageMultiplier_with_range": "Pattern Provider Slot Multiplier (1-64)",
+ "extendedae_plus.configuration.showEncoderPatternPlayer": "Show Pattern Encoder Player",
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "Pattern Terminal Show Slots by Default",
+ "extendedae_plus.configuration.craftingPauseThreshold": "AE Crafting Calculation Pause Threshold",
- "extendedae_plus.configuration.smartScaling": "Smart Scaling",
- "extendedae_plus.configuration.smartScalingMaxMultiplier": "Smart Scaling Max Multiplier",
- "extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Max Multiplier (0 = Unlimited)",
+ "extendedae_plus.configuration.smartScaling": "Smart Pattern Scaling",
+ "extendedae_plus.configuration.providerRoundRobinEnable": "Enable Pattern Provider Round-Robin Allocation",
+ "extendedae_plus.configuration.smartScalingMaxMultiplier": "Smart Scaling Maximum Multiplier",
+ "extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Maximum Multiplier (0 for Unlimited)",
+
+ "extendedae_plus.configuration.wireless": "Wireless Connector Configuration",
+ "extendedae_plus.configuration.wirelessMaxRange": "Wireless Maximum Range",
+ "extendedae_plus.configuration.wirelessMaxRange_with_range": "Wireless Maximum Range (1-4096)",
+ "extendedae_plus.configuration.wirelessCrossDimEnable": "Allow Wireless Transceiver Cross-Dimension Connection",
+
+ "extendedae_plus.configuration.entitySpeedTicker": "Entity Accelerator",
+ "extendedae_plus.configuration.entityTickerCost": "Entity Accelerator Base Energy Consumption",
+ "extendedae_plus.configuration.entityTickerBlackList": "Entity Accelerator Blacklist",
+ "extendedae_plus.configuration.entityTickerMultipliers": "Entity Accelerator Additional Consumption Multiplier",
- "extendedae_plus.configuration.wireless": "WirelessTransceiverBlock",
- "extendedae_plus.configuration.wirelessMaxRange": "Wireless Max Range",
- "extendedae_plus.configuration.wirelessMaxRange_with_range": "Wireless Max Range (1-4096)",
- "extendedae_plus.configuration.wirelessCrossDimEnable": "Allow Wireless Cross-Dimension",
"extendedae_plus.configuration.state_on": "On",
"extendedae_plus.configuration.state_off": "Off"
}
\ No newline at end of file
diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
index 1699f08..e894f81 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -3,9 +3,36 @@
"gui.expatternprovider.hide_slots": "隐藏槽位",
"gui.expatternprovider.show_slots": "显示槽位",
"gui.expatternprovider.clear_selection": "取消选择",
- "block.extendedae_plus.wireless_transceiver": "无线收发器",
- "item.extendedae_plus.wireless_transceiver": "无线收发器",
+ "gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式",
+ "gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡",
+ "gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍",
+ "gui.extendedae_plus.global.all_on": "全部开启",
+ "gui.extendedae_plus.global.all_off": "全部关闭",
+
"itemGroup.extendedae_plus.main": "扩展AE Plus",
+ "item.extendedae_plus.wireless_transceiver": "无线收发器",
+ "item.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
+ "item.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
+ "item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
+ "item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
+ "item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
+ "item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
+ "item.extendedae_plus.entity_speed_card": "实体加速卡",
+ "item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
+ "item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
+ "item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
+ "item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
+ "tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
+ "tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
+
+ "block.extendedae_plus.wireless_transceiver": "无线收发器",
+ "block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
+ "block.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
+ "block.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
+ "block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
+ "block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
+ "block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
+
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
"extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
@@ -16,18 +43,6 @@
"extendedae_plus.screen.cn_name": "中文名",
"extendedae_plus.button.choose_provider":"上传样板",
"extendedae_plus.pattern.hovertext.player": "由 %s 编码",
-
- "block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
- "block.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
- "block.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
- "block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
- "block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
-
- "item.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
- "item.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
- "item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
- "item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
- "item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
"item.extendedae_plus.entity_speed_ticker": "实体加速器",
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",
@@ -40,17 +55,6 @@
"item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
"item.extendedae_plus.entity_speed_ticker.tip.energy": "加速将消耗 AE 网络能量,网络能量不足时无法加速",
- "item.extendedae_plus.entity_speed_card": "实体加速卡",
- "item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
- "item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
- "item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
- "item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
- "tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
- "tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
- "item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静",
- "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物",
- "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰",
-
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",
"config.jade.plugin_extendedae_plus.wt_master_mode": "显示主/从模式",
@@ -69,7 +73,7 @@
"extendedae_plus.configuration.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
"extendedae_plus.configuration.showEncoderPatternPlayer": "显示样板编码玩家",
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
-
+ "extendedae_plus.configuration.craftingPauseThreshold": "AE合成计算暂停检查阈值",
"extendedae_plus.configuration.smartScaling": "智能样板倍增",
"extendedae_plus.configuration.providerRoundRobinEnable": "启用样板供应器轮询分配",
@@ -85,15 +89,7 @@
"extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值",
"extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单",
"extendedae_plus.configuration.entityTickerMultipliers": "实体加速器额外消耗倍率",
- "extendedae_pluscraftingPauseThreshold": "AE合成计算暂停检查阈值",
"extendedae_plus.configuration.state_on": "开",
- "extendedae_plus.configuration.state_off": "关",
- "block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
- "item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
- "gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式",
- "gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡",
- "gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍",
- "gui.extendedae_plus.global.all_on": "全部开启",
- "gui.extendedae_plus.global.all_off": "全部关闭"
+ "extendedae_plus.configuration.state_off": "关"
}
\ No newline at end of file
From 9966205aa20c8a46a13c36fcb782b2e602ec169e Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sun, 21 Sep 2025 18:16:27 +0800
Subject: [PATCH 12/43] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E7=AB=AF=E5=B4=A9=E6=BA=83=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 5 ++++-
.../network/ProvidersListS2CPacket.java | 17 +++++++++++------
src/main/resources/extendedae_plus.mixins.json | 2 +-
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/build.gradle b/build.gradle
index 7394979..3dd7ba2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -166,11 +166,14 @@ dependencies {
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
//aea
- implementation "curse.maven:advancedae-1084104:6921281"
+ implementation "curse.maven:advancedae-1084104:6932981"
//geckolib
runtimeOnly "curse.maven:geckolib-388172:7009924"
+
+ runtimeOnly fileTree(dir: 'libs', includes: ['*.jar'])
+
// setup Xei (EMI/REI/JEI) using project property 'use_Xei'
switch (project.findProperty('use_Xei') ?: 'emi') {
case 'emi':
diff --git a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java b/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java
index 0fa8184..2da91df 100644
--- a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java
+++ b/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java
@@ -7,6 +7,8 @@ import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
+import net.neoforged.api.distmarker.Dist;
+import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.ArrayList;
@@ -58,11 +60,14 @@ public class ProvidersListS2CPacket implements CustomPacketPayload {
}
public static void handle(final ProvidersListS2CPacket msg, final IPayloadContext ctx) {
- ctx.enqueueWork(() -> {
- var mc = Minecraft.getInstance();
- if (mc == null) return;
- var current = mc.screen;
- mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots));
- });
+ ctx.enqueueWork(() -> handleClient(msg));
+ }
+
+ @OnlyIn(Dist.CLIENT)
+ private static void handleClient(ProvidersListS2CPacket msg) {
+ var mc = Minecraft.getInstance();
+ if (mc == null) return;
+ var current = mc.screen;
+ mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots));
}
}
diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json
index f7a85bd..034dc6d 100644
--- a/src/main/resources/extendedae_plus.mixins.json
+++ b/src/main/resources/extendedae_plus.mixins.json
@@ -14,7 +14,6 @@
"ae2.CraftingCalculationMixin",
"ae2.CraftingCPUClusterMixin",
"ae2.EncodedPatternItemMixin",
- "ae2.QuartzCuttingKnifeItemMixin",
"ae2.accessor.MEStorageMenuAccessor",
"ae2.accessor.PatternEncodingTermMenuAccessor",
"ae2.accessor.PatternProviderLogicAccessor",
@@ -46,6 +45,7 @@
"accessor.ScreenAccessor",
"advancedae.client.gui.AdvPatternProviderScreenMixin",
"advancedae.client.gui.SmallAdvPatternProviderScreenMixin",
+ "ae2.QuartzCuttingKnifeItemMixin",
"ae2.accessor.AEBaseScreenAccessor",
"ae2.accessor.AEBaseScreenInvoker",
"ae2.accessor.MEStorageScreenAccessor",
From 9c7d49bb7ec9062cfb248250a50759c35f910f94 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sun, 21 Sep 2025 19:37:46 +0800
Subject: [PATCH 13/43] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4lang=E6=96=87?=
=?UTF-8?q?=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/extendedae_plus/ExtendedAEPlus.java | 4 +-
.../extendedae_plus/config/ModConfigs.java | 120 ++++++++----------
.../mixin/ae2/EncodedPatternItemMixin.java | 8 +-
.../assets/extendedae_plus/lang/en_us.json | 25 ++--
.../assets/extendedae_plus/lang/zh_cn.json | 9 +-
5 files changed, 80 insertions(+), 86 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 3f6640a..5a01c42 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -54,7 +54,9 @@ public class ExtendedAEPlus {
NeoForge.EVENT_BUS.register(this);
// 注册配置:接入自定义的 ModConfigs
- modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
+ modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC, "extendedae_plus-common.toml");
+ modContainer.registerConfig(ModConfig.Type.CLIENT, ModConfigs.CLIENT_SPEC, "extendedae_plus-client.toml");
+ modContainer.registerConfig(ModConfig.Type.SERVER, ModConfigs.SERVER_SPEC, "extendedae_plus-server.toml");
}
// 便捷 ResourceLocation 工具
diff --git a/src/main/java/com/extendedae_plus/config/ModConfigs.java b/src/main/java/com/extendedae_plus/config/ModConfigs.java
index 5abe719..bd6665e 100644
--- a/src/main/java/com/extendedae_plus/config/ModConfigs.java
+++ b/src/main/java/com/extendedae_plus/config/ModConfigs.java
@@ -5,61 +5,62 @@ import net.neoforged.neoforge.common.ModConfigSpec;
import java.util.List;
public final class ModConfigs {
+ // Common 配置
public static final ModConfigSpec COMMON_SPEC;
public static final ModConfigSpec.IntValue PAGE_MULTIPLIER;
- public static final ModConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
- public static final ModConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
- public static final ModConfigSpec.BooleanValue SHOW_ENCOD_PATTERN_PLAYER;
- public static final ModConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
+
+ // Client 配置
+ public static final ModConfigSpec CLIENT_SPEC;
+ public static final ModConfigSpec.BooleanValue SHOW_ENCODER_PATTERN_PLAYER;
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
+
+ // Server 配置
+ public static final ModConfigSpec SERVER_SPEC;
+ public static final ModConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
public static final ModConfigSpec.IntValue CRAFTING_PAUSE_THRESHOLD;
+ public static final ModConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
+ public static final ModConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
public static final ModConfigSpec.IntValue ENTITY_TICKER_COST;
public static final ModConfigSpec.ConfigValue> ENTITY_TICKER_BLACK_LIST;
public static final ModConfigSpec.ConfigValue> ENTITY_TICKER_MULTIPLIERS;
- static {
- ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
- // General settings
- builder.push("general");
- PAGE_MULTIPLIER = builder
+ static {
+ // Common 配置
+ ModConfigSpec.Builder commonBuilder = new ModConfigSpec.Builder();
+ commonBuilder.push("common");
+ PAGE_MULTIPLIER = commonBuilder
.comment(
"扩展样板供应器总槽位容量的倍率",
"基础为36,每页仍显示36格,倍率会增加总页数/总容量",
"建议范围 1-16"
)
.defineInRange("pageMultiplier", 1, 1, 64);
+ commonBuilder.pop();
+ COMMON_SPEC = commonBuilder.build();
- // 是否显示样板编码玩家(通用)
- SHOW_ENCOD_PATTERN_PLAYER = builder
+ // Client 配置
+ ModConfigSpec.Builder clientBuilder = new ModConfigSpec.Builder();
+ clientBuilder.push("client");
+ SHOW_ENCODER_PATTERN_PLAYER = clientBuilder
.comment(
"是否显示样板编码玩家",
"开启后将在样板 HoverText 上添加样板的编码玩家"
)
.define("showEncoderPatternPlayer", true);
-
- // 模式访问终端(ExtendedAE 图样终端)默认是否显示槽位渲染(SlotsRow)。
- // true: 默认显示(可通过界面按钮临时隐藏);false: 默认隐藏(可通过按钮显示)
- PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT = builder
+ PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT = clientBuilder
.comment(
"样板终端默认是否显示槽位",
"影响进入界面时SlotsRow的默认可见性,仅影响客户端显示"
)
.define("patternTerminalShowSlotsDefault", true);
+ clientBuilder.pop();
+ CLIENT_SPEC = clientBuilder.build();
- CRAFTING_PAUSE_THRESHOLD = builder
- .comment(
- "值越大将减少AE构建合成计划过程中的 wait/notify 次数,提升吞吐但会降低调度响应性"
- )
- .defineInRange("craftingPauseThreshold", 100000, 100, Integer.MAX_VALUE);
-
- // end general
- builder.pop();
-
- // Smart-scaling group
- builder.push("smartScaling");
- // 智能倍增:是否在样板供应器间轮询分配请求量(开启:按 provider 均分;关闭:不拆分)
- PROVIDER_ROUND_ROBIN_ENABLE = builder
+ // Server 配置
+ ModConfigSpec.Builder serverBuilder = new ModConfigSpec.Builder();
+ serverBuilder.push("ae");
+ PROVIDER_ROUND_ROBIN_ENABLE = serverBuilder
.comment(
"智能倍增时是否对样板供应器轮询分配",
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
@@ -67,75 +68,64 @@ public final class ModConfigs {
"默认: true"
)
.define("providerRoundRobinEnable", true);
-
- // 智能倍增的最大倍数(以单次样板产出为单位)。
- // 0 表示不限制;>0 表示最大倍增倍数上限,例如 64 表示最多放大到 64 倍。
- SMART_SCALING_MAX_MULTIPLIER = builder
+ SMART_SCALING_MAX_MULTIPLIER = serverBuilder
.comment(
"智能倍增的最大倍数(0 表示不限制)",
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模"
)
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
+ CRAFTING_PAUSE_THRESHOLD = serverBuilder
+ .comment(
+ "值越大将减少AE构建合成计划过程中的 wait/notify 次数,提升吞吐但会降低调度响应性"
+ )
+ .defineInRange("craftingPauseThreshold", 100000, 100, Integer.MAX_VALUE);
+ serverBuilder.pop();
- builder.pop(); // pop smart
-
- // Wireless settings
- builder.push("wireless");
- // 无线收发器:最大连接距离(单位:方块)。
- // 一对多从端连接主端时,将以该值作为范围限制。
- WIRELESS_MAX_RANGE = builder
+ serverBuilder.push("wireless");
+ WIRELESS_MAX_RANGE = serverBuilder
.comment(
"无线收发器最大连接距离(单位:方块)",
"从端与主端的直线距离需小于等于该值才会建立连接。"
)
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
-
- // 是否允许跨维度连接(忽略维度差异进行频道传输)。
- WIRELESS_CROSS_DIM_ENABLE = builder
+ WIRELESS_CROSS_DIM_ENABLE = serverBuilder
.comment(
"是否允许无线收发器跨维度建立连接",
"开启后,从端可连接到不同维度的主端(忽略距离限制)"
)
.define("wirelessCrossDimEnable", true);
+ serverBuilder.pop();
- builder.pop(); // pop wireless
-
- builder.push("entitySpeedTicker");
-
- ENTITY_TICKER_COST = builder
+ serverBuilder.push("entitySpeedTicker");
+ ENTITY_TICKER_COST = serverBuilder
.comment(
"实体加速器能量消耗基础值"
)
- .defineInRange("entityTickerCost", 512, 0 , Integer.MAX_VALUE);
-
-
- ENTITY_TICKER_BLACK_LIST = builder
+ .defineInRange("entityTickerCost", 512, 0, Integer.MAX_VALUE);
+ ENTITY_TICKER_BLACK_LIST = serverBuilder
.comment(
"实体加速器黑名单:匹配的方块将不会被加速。支持通配符/正则(例如:minecraft:*)",
"格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
)
.defineListAllowEmpty(
- List.of("entityTickerBlackList"), // 路径
- List::of, // 默认值
- () -> "", // 新元素默认值(空字符串,供配置编辑器使用)
- obj -> obj instanceof String // 验证每个元素是字符串
+ List.of("entityTickerBlackList"),
+ List::of,
+ () -> "",
+ obj -> obj instanceof String
);
-
- ENTITY_TICKER_MULTIPLIERS = builder
+ ENTITY_TICKER_MULTIPLIERS = serverBuilder
.comment(
"额外消耗倍率配置:为某些方块设置额外能量倍率,格式 'modid:blockid multiplier',例如 'minecraft:chest 2x'",
"支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
)
.defineListAllowEmpty(
- List.of("entityTickerMultipliers"), // 路径
- List::of, // 默认值
- () -> "", // 新元素默认值(空字符串,供配置编辑器使用)
- obj -> obj instanceof String // 验证每个元素是字符串
+ List.of("entityTickerMultipliers"),
+ List::of,
+ () -> "",
+ obj -> obj instanceof String
);
-
- builder.pop();
-
- COMMON_SPEC = builder.build();
+ serverBuilder.pop();
+ SERVER_SPEC = serverBuilder.build();
}
private ModConfigs() {
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/EncodedPatternItemMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/EncodedPatternItemMixin.java
index efb4106..619368d 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/EncodedPatternItemMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/EncodedPatternItemMixin.java
@@ -5,10 +5,10 @@ import com.extendedae_plus.config.ModConfigs;
import net.minecraft.ChatFormatting;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.chat.Component;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.item.component.CustomData;
-import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.Item;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.TooltipFlag;
+import net.minecraft.world.item.component.CustomData;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -21,7 +21,7 @@ public class EncodedPatternItemMixin {
// 客户端:在 HoverText 显示样板的编码玩家
@Inject(method = "appendHoverText", at = @At("TAIL"))
public void epp$appendHoverText(ItemStack stack, Item.TooltipContext context, List lines, TooltipFlag advancedTooltips, CallbackInfo ci){
- if (ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.get()) {
+ if (ModConfigs.SHOW_ENCODER_PATTERN_PLAYER.get()) {
var customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
var tag = customData.copyTag();
if (tag.contains("encodePlayer")) {
diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json
index 9a16f0c..babef8e 100644
--- a/src/main/resources/assets/extendedae_plus/lang/en_us.json
+++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json
@@ -67,28 +67,29 @@
"screen.extendedae_plus.title": "ExtendedAE Plus Configuration",
"config.extendedae_plus": "ExtendedAE Plus",
-
- "extendedae_plus.configuration.general": "General",
+
+ "extendedae_plus.configuration.common": "Common Configuration",
"extendedae_plus.configuration.pageMultiplier": "Pattern Provider Slot Multiplier",
"extendedae_plus.configuration.pageMultiplier_with_range": "Pattern Provider Slot Multiplier (1-64)",
+
+ "extendedae_plus.configuration.client": "Display Configuration",
"extendedae_plus.configuration.showEncoderPatternPlayer": "Show Pattern Encoder Player",
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "Pattern Terminal Show Slots by Default",
- "extendedae_plus.configuration.craftingPauseThreshold": "AE Crafting Calculation Pause Threshold",
+ "extendedae_plus.configuration.ae": "AE2 Configuration",
+ "extendedae_plus.configuration.craftingPauseThreshold": "AE Crafting Pause Threshold",
"extendedae_plus.configuration.smartScaling": "Smart Pattern Scaling",
- "extendedae_plus.configuration.providerRoundRobinEnable": "Enable Pattern Provider Round-Robin Allocation",
+ "extendedae_plus.configuration.providerRoundRobinEnable": "Enable Pattern Provider Round-Robin",
"extendedae_plus.configuration.smartScalingMaxMultiplier": "Smart Scaling Maximum Multiplier",
- "extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Maximum Multiplier (0 for Unlimited)",
-
+ "extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Maximum Multiplier (0 for unlimited)",
"extendedae_plus.configuration.wireless": "Wireless Connector Configuration",
"extendedae_plus.configuration.wirelessMaxRange": "Wireless Maximum Range",
"extendedae_plus.configuration.wirelessMaxRange_with_range": "Wireless Maximum Range (1-4096)",
- "extendedae_plus.configuration.wirelessCrossDimEnable": "Allow Wireless Transceiver Cross-Dimension Connection",
-
- "extendedae_plus.configuration.entitySpeedTicker": "Entity Accelerator",
- "extendedae_plus.configuration.entityTickerCost": "Entity Accelerator Base Energy Consumption",
- "extendedae_plus.configuration.entityTickerBlackList": "Entity Accelerator Blacklist",
- "extendedae_plus.configuration.entityTickerMultipliers": "Entity Accelerator Additional Consumption Multiplier",
+ "extendedae_plus.configuration.wirelessCrossDimEnable": "Enable Wireless Cross-Dimensional Connection",
+ "extendedae_plus.configuration.entitySpeedTicker": "Entity Speed Ticker",
+ "extendedae_plus.configuration.entityTickerCost": "Entity Ticker Base Energy Cost",
+ "extendedae_plus.configuration.entityTickerBlackList": "Entity Ticker Blacklist",
+ "extendedae_plus.configuration.entityTickerMultipliers": "Entity Ticker Extra Cost Multipliers",
"extendedae_plus.configuration.state_on": "On",
"extendedae_plus.configuration.state_off": "Off"
diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
index e894f81..8592356 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -68,23 +68,24 @@
"screen.extendedae_plus.title": "ExtendedAE Plus 配置",
"config.extendedae_plus": "ExtendedAE Plus",
- "extendedae_plus.configuration.general": "通用",
+ "extendedae_plus.configuration.common": "通用配置",
"extendedae_plus.configuration.pageMultiplier": "扩展样板供应器槽位倍率",
"extendedae_plus.configuration.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
+
+ "extendedae_plus.configuration.client": "显示配置",
"extendedae_plus.configuration.showEncoderPatternPlayer": "显示样板编码玩家",
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
- "extendedae_plus.configuration.craftingPauseThreshold": "AE合成计算暂停检查阈值",
+ "extendedae_plus.configuration.ae": "AE2配置",
+ "extendedae_plus.configuration.craftingPauseThreshold": "AE合成计算暂停检查阈值",
"extendedae_plus.configuration.smartScaling": "智能样板倍增",
"extendedae_plus.configuration.providerRoundRobinEnable": "启用样板供应器轮询分配",
"extendedae_plus.configuration.smartScalingMaxMultiplier": "智能倍增最大倍数",
"extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "智能倍增最大倍数 (0为不限制)",
-
"extendedae_plus.configuration.wireless": "无线连接器配置",
"extendedae_plus.configuration.wirelessMaxRange": "无线最大距离",
"extendedae_plus.configuration.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
"extendedae_plus.configuration.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
-
"extendedae_plus.configuration.entitySpeedTicker": "实体加速器",
"extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值",
"extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单",
From 4de90feae20f2cb19bc05196919c522a47983c97 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Sun, 21 Sep 2025 21:31:10 +0800
Subject: [PATCH 14/43] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=83=8C?=
=?UTF-8?q?=E5=8C=85=E6=9C=89=E7=BD=91=E7=BB=9C=E5=B7=A5=E5=85=B7=E6=89=93?=
=?UTF-8?q?=E5=BC=80=E5=AE=9E=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8=E7=95=8C?=
=?UTF-8?q?=E9=9D=A2=E5=B4=A9=E6=BA=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../assets/ae2/screens/entity_speed_ticker.json | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/main/resources/assets/ae2/screens/entity_speed_ticker.json b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json
index e30da1a..aaeab6c 100644
--- a/src/main/resources/assets/ae2/screens/entity_speed_ticker.json
+++ b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json
@@ -2,7 +2,8 @@
"$schema": "schema.json",
"includes": [
"common/common.json",
- "common/player_inventory.json"
+ "common/player_inventory.json",
+ "common/toolbox.json"
],
"background": {
"texture": "guis/blank_background.png",
@@ -62,16 +63,14 @@
},
"widgets": {
"toolbox": {
- "right": -2,
- "bottom": 50,
- "width": 68,
- "height": 68
+ "right": 1,
+ "bottom": 55
}
},
"slots": {
"TOOLBOX": {
- "bottom": 42,
- "right": -10,
+ "right": 0,
+ "bottom": 50,
"grid": "BREAK_AFTER_3COLS"
}
}
From 47aa90a4388c73474bafcc549a7931439d827b7d Mon Sep 17 00:00:00 2001
From: GaLicn <133291877+GaLicn@users.noreply.github.com>
Date: Wed, 24 Sep 2025 12:42:48 +0800
Subject: [PATCH 15/43] =?UTF-8?q?=E8=A3=85=E9=85=8D=E7=9F=A9=E9=98=B5?=
=?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=A0=B8=E5=BF=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 2 +-
.../modMetadata/META-INF/neoforge.mods.toml | 103 ++++++++++++++++++
gradle.properties | 2 +-
.../com/extendedae_plus/ExtendedAEPlus.java | 11 +-
.../content/matrix/UploadCoreBlock.java | 26 +++++
.../content/matrix/UploadCoreBlockEntity.java | 24 ++++
.../init/ModBlockEntities.java | 7 ++
.../com/extendedae_plus/init/ModBlocks.java | 12 ++
.../extendedae_plus/init/ModCreativeTabs.java | 1 +
.../com/extendedae_plus/init/ModItems.java | 6 +
.../util/ExtendedAEPatternUploadUtil.java | 24 +++-
.../assembler_matrix_upload_core.json | 5 +
.../assets/extendedae_plus/lang/en_us.json | 2 +
.../assets/extendedae_plus/lang/zh_cn.json | 2 +
.../block/assembler_matrix_upload_core.json | 6 +
.../item/assembler_matrix_upload_core.json | 3 +
.../block/assembler_matrix_upload_core.png | Bin 0 -> 225 bytes
.../textures/item/channel_card.png | Bin 0 -> 455 bytes
.../recipes/assembler_matrix_upload_core.json | 11 ++
19 files changed, 243 insertions(+), 4 deletions(-)
create mode 100644 build/generated/sources/modMetadata/META-INF/neoforge.mods.toml
create mode 100644 src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java
create mode 100644 src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java
create mode 100644 src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json
create mode 100644 src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png
create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/channel_card.png
create mode 100644 src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json
diff --git a/build.gradle b/build.gradle
index 3dd7ba2..cbb9f40 100644
--- a/build.gradle
+++ b/build.gradle
@@ -139,7 +139,7 @@ dependencies {
implementation "curse.maven:curios-309927:6529130"
compileOnly "curse.maven:ex-pattern-provider-892005:6863556"
- compileOnly "curse.maven:applied-flux-965012:5614830"
+ implementation "curse.maven:applied-flux-965012:5614830"
compileOnly "dev.emi:emi-neoforge:1.1.10+1.21"
compileOnly "curse.maven:mega-cells-622112:6005043"
compileOnly "curse.maven:jade-324717:5427817"
diff --git a/build/generated/sources/modMetadata/META-INF/neoforge.mods.toml b/build/generated/sources/modMetadata/META-INF/neoforge.mods.toml
new file mode 100644
index 0000000..1ad0ea3
--- /dev/null
+++ b/build/generated/sources/modMetadata/META-INF/neoforge.mods.toml
@@ -0,0 +1,103 @@
+# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
+# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
+# The overall format is standard TOML format, v0.5.0.
+# Note that there are a couple of TOML lists in this file.
+# Find more information on toml format here: https://github.com/toml-lang/toml
+
+# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
+modLoader="javafml" #mandatory
+
+# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2.
+loaderVersion="[1,)" #mandatory
+
+# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
+# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
+license="All Rights Reserved"
+
+# A URL to refer people to when problems occur with this mod
+#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
+
+# A list of mods - how many allowed here is determined by the individual mod loader
+[[mods]] #mandatory
+
+# The modid of the mod
+modId="extendedae_plus" #mandatory
+
+# The version number of the mod
+version="1.21.1-1.4.2" #mandatory
+
+# A display name for the mod
+displayName="ExtendedAE-Plus" #mandatory
+
+# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
+#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
+
+# A URL for the "homepage" for this mod, displayed in the mod UI
+#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
+
+# A file name (in the root of the mod JAR) containing a logo for display
+#logoFile="examplemod.png" #optional
+
+# A text field displayed in the mod UI
+#credits="" #optional
+
+# A text field displayed in the mod UI
+authors="YourNameHere, OtherNameHere" #optional
+
+# The description text for the mod (multi line!) (#mandatory)
+description='''Example mod description.
+Newline characters can be used and will be replaced properly.'''
+
+# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
+[[mixins]]
+config="extendedae_plus.mixins.json"
+
+# The [[accessTransformers]] block allows you to declare where your AT file is.
+# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
+#[[accessTransformers]]
+#file="META-INF/accesstransformer.cfg"
+
+# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
+
+# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
+[[dependencies.extendedae_plus]] #optional
+ # the modid of the dependency
+ modId="neoforge" #mandatory
+ # The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
+ # 'required' requires the mod to exist, 'optional' does not
+ # 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
+ type="required" #mandatory
+ # Optional field describing why the dependency is required or why it is incompatible
+ # reason="..."
+ # The version range of the dependency
+ versionRange="[21.1.1,)" #mandatory
+ # An ordering relationship for the dependency.
+ # BEFORE - This mod is loaded BEFORE the dependency
+ # AFTER - This mod is loaded AFTER the dependency
+ ordering="NONE"
+ # Side this dependency is applied on - BOTH, CLIENT, or SERVER
+ side="BOTH"
+
+# Here's another dependency
+[[dependencies.extendedae_plus]]
+ modId="minecraft"
+ type="required"
+ # This version range declares a minimum of the current minecraft version up to but not including the next major version
+ versionRange="[1.21.1]"
+ ordering="NONE"
+ side="BOTH"
+
+# Require ExtendedAE (ExtendedAE-1.21-2.2.21-neoforge) to be present
+[[dependencies.extendedae_plus]]
+ modId="extendedae"
+ type="required"
+ # Use a permissive range to tolerate upstream version string variations (e.g. 1.21-2.2.21-neoforge)
+ versionRange="*"
+ ordering="AFTER"
+ side="BOTH"
+
+# Features are specific properties of the game environment, that you may want to declare you require. This example declares
+# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
+# stop your mod loading on the server for example.
+#[features.extendedae_plus]
+#openGLVersion="[3.2,)"
diff --git a/gradle.properties b/gradle.properties
index ed7c9ca..ebafd76 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -32,7 +32,7 @@ mod_name=ExtendedAE-Plus
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved
# The mod version. See https://semver.org/
-mod_version=1.21.1-1.4.0-fix
+mod_version=1.21.1-1.4.2
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 5a01c42..fa57422 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -110,8 +110,17 @@ public class ExtendedAEPlus {
b256.setBlockEntity(CraftingBlockEntity.class, type, null, null);
b1024.setBlockEntity(CraftingBlockEntity.class, type, null, null);
LOGGER.info("Bound AE2 CraftingBlockEntity to ExtendedAE Plus accelerators.");
+
+ // 绑定装配矩阵上传核心方块实体类型,避免 blockEntityClass 为 null 的问题
+ ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get().setBlockEntity(
+ com.extendedae_plus.content.matrix.UploadCoreBlockEntity.class,
+ ModBlockEntities.UPLOAD_CORE_BE.get(),
+ null,
+ null
+ );
+ LOGGER.info("Bound UploadCoreBlockEntity to assembler matrix upload core block.");
} catch (Throwable t) {
- LOGGER.warn("Failed to bind CraftingBlockEntity to accelerators: {}", t.toString());
+ LOGGER.warn("Failed to bind block entities: {}", t.toString());
}
});
}
diff --git a/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java
new file mode 100644
index 0000000..d4ec40c
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java
@@ -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 {
+
+ 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();
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java
new file mode 100644
index 0000000..8ca5ffb
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java
@@ -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 工具类或事件中做校验与提示。
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java
index 9ba9b57..c3b9ed7 100644
--- a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java
+++ b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java
@@ -3,6 +3,7 @@ package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
+import com.extendedae_plus.content.matrix.UploadCoreBlockEntity;
import appeng.blockentity.crafting.CraftingBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.core.registries.Registries;
@@ -42,4 +43,10 @@ public final class ModBlockEntities {
ref.set(type);
return type;
});
+
+ // 装配矩阵上传核心方块实体
+ public static final DeferredHolder, BlockEntityType> UPLOAD_CORE_BE =
+ BLOCK_ENTITY_TYPES.register("upload_core",
+ () -> BlockEntityType.Builder.of(UploadCoreBlockEntity::new,
+ ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get()).build(null));
}
diff --git a/src/main/java/com/extendedae_plus/init/ModBlocks.java b/src/main/java/com/extendedae_plus/init/ModBlocks.java
index 2944276..d1e116f 100644
--- a/src/main/java/com/extendedae_plus/init/ModBlocks.java
+++ b/src/main/java/com/extendedae_plus/init/ModBlocks.java
@@ -3,6 +3,7 @@ package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.content.wireless.WirelessTransceiverBlock;
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
+import com.extendedae_plus.content.matrix.UploadCoreBlock;
import appeng.block.crafting.CraftingUnitBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
import appeng.core.definitions.AEBlockEntities;
@@ -73,4 +74,15 @@ public final class ModBlocks {
return new CraftingUnitBlock(EPlusCraftingUnitType.ACCELERATOR_1024x);
}
);
+
+ // 装配矩阵上传核心方块
+ public static final DeferredBlock ASSEMBLER_MATRIX_UPLOAD_CORE = BLOCKS.register(
+ "assembler_matrix_upload_core",
+ () -> new UploadCoreBlock(
+ BlockBehaviour.Properties.of()
+ .mapColor(MapColor.METAL)
+ .strength(1.5F, 6.0F)
+ .requiresCorrectToolForDrops()
+ )
+ );
}
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index 10d9144..2b5cb0b 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -23,6 +23,7 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_64x.get());
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
+ output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get());
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
// 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
output.accept(ModItems.createEntitySpeedCardStack((byte) 2));
diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java
index e985c1e..8678005 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -68,4 +68,10 @@ public final class ModItems {
public static ItemStack createEntitySpeedCardStack(byte multiplier) {
return EntitySpeedCardItem.withMultiplier(multiplier);
}
+
+ // 装配矩阵上传核心物品
+ public static final DeferredItem- ASSEMBLER_MATRIX_UPLOAD_CORE = ITEMS.register(
+ "assembler_matrix_upload_core",
+ () -> new BlockItem(ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get(), new Item.Properties())
+ );
}
diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java
index c49cf6f..020e3c0 100644
--- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java
+++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java
@@ -525,7 +525,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);
@@ -1180,4 +1180,26 @@ public class ExtendedAEPatternUploadUtil {
}
return false;
}
+
+ /**
+ * 判断给定矩阵集群中是否存在"装配矩阵上传核心"。
+ * 要求:至少存在 1 个即可,不限制数量。
+ * 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。
+ */
+ private static boolean clusterHasSingleUploadCore(com.glodblock.github.extendedae.common.tileentities.matrix.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;
+ }
+ }
}
diff --git a/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json
new file mode 100644
index 0000000..a099eb5
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json
@@ -0,0 +1,5 @@
+{
+ "variants": {
+ "": { "model": "extendedae_plus:block/assembler_matrix_upload_core" }
+ }
+}
diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json
index babef8e..30340f1 100644
--- a/src/main/resources/assets/extendedae_plus/lang/en_us.json
+++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json
@@ -17,6 +17,7 @@
"item.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
"item.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
"item.extendedae_plus.network_pattern_controller": "Pattern Provider Status Controller",
+ "item.extendedae_plus.assembler_matrix_upload_core": "Assembly Matrix Upload Core",
"item.extendedae_plus.entity_speed_card": "Entity Speed Card",
"item.extendedae_plus.entity_speed_card.x2": "Entity Speed Card (x2)",
"item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
@@ -32,6 +33,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 Provider Status Controller",
+ "block.extendedae_plus.assembler_matrix_upload_core": "Assembly Matrix Upload Core",
"extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix",
"extendedae_plus.upload_to_matrix.success": "Pattern Uploaded to Assembly Matrix",
diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
index 8592356..b026489 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -17,6 +17,7 @@
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
"item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
+ "item.extendedae_plus.assembler_matrix_upload_core": "装配矩阵上传核心",
"item.extendedae_plus.entity_speed_card": "实体加速卡",
"item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
@@ -32,6 +33,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": "样板已上传到装配矩阵",
diff --git a/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json
new file mode 100644
index 0000000..768aff9
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json
@@ -0,0 +1,6 @@
+{
+ "parent": "minecraft:block/cube_all",
+ "textures": {
+ "all": "extendedae_plus:block/assembler_matrix_upload_core"
+ }
+}
diff --git a/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json
new file mode 100644
index 0000000..5470af1
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json
@@ -0,0 +1,3 @@
+{
+ "parent": "extendedae_plus:block/assembler_matrix_upload_core"
+}
diff --git a/src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png b/src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png
new file mode 100644
index 0000000000000000000000000000000000000000..bac350b1ca8ac3c6423aaea69b4134ef3d5ea517
GIT binary patch
literal 225
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|W_!9ghFJ6_
z??>IlPn_yFcrzm=Qqph*?}?feHY4$My`3uj
z3MLC?c5y!7nsMO|1EbU4SwM6`>Bk(EE30QlOgS}Czj+z^ayB-$wx{*mU(cIxNb6{m
z=9hIpb_J||mf5EMzH5E*DWiM4|Ib*`arj-Pk3*e}!XE}hX8%hK43qvIU|QHwF2wN7
X%K4y}yVzc!GZ{Qx{an^LB{Ts5t+`n7
literal 0
HcmV?d00001
diff --git a/src/main/resources/assets/extendedae_plus/textures/item/channel_card.png b/src/main/resources/assets/extendedae_plus/textures/item/channel_card.png
new file mode 100644
index 0000000000000000000000000000000000000000..2fe67ab91aa171c096bdf1c628d56947b2663360
GIT binary patch
literal 455
zcmV;&0XY7NP)oOTmiB@DmIl35%|JuY>F2;O!3E$3d;}_*)Yk`AHwmg5
xM&r^95{Cy$a#9jFGr=+qx+ZJ^h^~e#J^)VZn#5&}mr?)#002ovPDHLkV1gDyzjXir
literal 0
HcmV?d00001
diff --git a/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json b/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json
new file mode 100644
index 0000000..12ca196
--- /dev/null
+++ b/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json
@@ -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
+ }
+}
From 0571efe2f908920af4ce4a59269fc6fcdfe1f027 Mon Sep 17 00:00:00 2001
From: GaLicn <133291877+GaLicn@users.noreply.github.com>
Date: Wed, 24 Sep 2025 13:04:46 +0800
Subject: [PATCH 16/43] =?UTF-8?q?=E9=A2=91=E9=81=93=E5=8D=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ae/items/ChannelCardItem.java | 66 +++++
.../bridge/InterfaceWirelessLinkBridge.java | 50 ++++
.../extendedae_plus/init/ModCreativeTabs.java | 1 +
.../com/extendedae_plus/init/ModItems.java | 7 +
.../InterfaceLogicChannelCardMixin.java | 225 ++++++++++++++++++
.../automation/IOBusPartChannelCardMixin.java | 145 +++++++++++
.../StorageBusPartChannelCardMixin.java | 141 +++++++++++
.../endpoint/GenericNodeEndpointImpl.java | 49 ++++
.../endpoint/InterfaceNodeEndpointImpl.java | 49 ++++
.../assets/extendedae_plus/lang/en_us.json | 4 +
.../assets/extendedae_plus/lang/zh_cn.json | 4 +
.../models/item/channel_card.json | 6 +
.../extendedae_plus/recipes/channel_card.json | 8 +
.../resources/extendedae_plus.mixins.json | 3 +
14 files changed, 758 insertions(+)
create mode 100644 src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java
create mode 100644 src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java
create mode 100644 src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java
create mode 100644 src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java
create mode 100644 src/main/resources/assets/extendedae_plus/models/item/channel_card.json
create mode 100644 src/main/resources/data/extendedae_plus/recipes/channel_card.json
diff --git a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java
new file mode 100644
index 0000000..6a922c4
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java
@@ -0,0 +1,66 @@
+package com.extendedae_plus.ae.items;
+
+import appeng.items.materials.UpgradeCardItem;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.component.CustomData;
+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.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ tag.putLong(TAG_CHANNEL, channel);
+ stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
+ }
+
+ public static long getChannel(ItemStack stack) {
+ CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ return tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L;
+ }
+
+ @Override
+ public void appendHoverText(ItemStack stack, Item.TooltipContext context, List lines, TooltipFlag flag) {
+ super.appendHoverText(stack, context, 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 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);
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java
new file mode 100644
index 0000000..d200f77
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java
@@ -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() {
+ // 默认实现为空
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index 2b5cb0b..77d059a 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -24,6 +24,7 @@ public final class ModCreativeTabs {
output.accept(ModItems.ACCELERATOR_256x.get());
output.accept(ModItems.ACCELERATOR_1024x.get());
output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get());
+ output.accept(ModItems.CHANNEL_CARD.get());
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
// 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
output.accept(ModItems.createEntitySpeedCardStack((byte) 2));
diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java
index 8678005..5189c54 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -3,6 +3,7 @@ package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem;
+import com.extendedae_plus.ae.items.ChannelCardItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
@@ -62,6 +63,12 @@ public final class ModItems {
() -> new EntitySpeedCardItem(new Item.Properties())
);
+ // 频道卡:用于AE机器的无线频道连接
+ public static final DeferredItem CHANNEL_CARD = ITEMS.register(
+ "channel_card",
+ () -> new ChannelCardItem(new Item.Properties())
+ );
+
/**
* 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
*/
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
new file mode 100644
index 0000000..15fd6f3
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
@@ -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, net.minecraft.core.HolderLookup.Provider registries, 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;
+ }
+
+ // 检查网格节点是否在线(1.21.1版本使用isActive替代hasGridBooted)
+ if (mainNode == null || !mainNode.isActive()) {
+ 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 == null || !mainNode.isActive()) {
+ // 仍在引导,消耗计时器
+ 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方法已在上面实现
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java
new file mode 100644
index 0000000..8f15d70
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java
@@ -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 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, net.minecraft.core.HolderLookup.Provider registries, 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;
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java
new file mode 100644
index 0000000..54a0bdb
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java
@@ -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, net.minecraft.core.HolderLookup.Provider registries, 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;
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java
new file mode 100644
index 0000000..84cd191
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java
@@ -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 blockEntitySupplier;
+ private final Supplier nodeSupplier;
+
+ public GenericNodeEndpointImpl(Supplier blockEntitySupplier, Supplier 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();
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java
new file mode 100644
index 0000000..6736f4c
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java
@@ -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 nodeSupplier;
+
+ public InterfaceNodeEndpointImpl(InterfaceLogicHost host, Supplier 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();
+ }
+}
diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json
index 30340f1..764c9ae 100644
--- a/src/main/resources/assets/extendedae_plus/lang/en_us.json
+++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json
@@ -18,6 +18,10 @@
"item.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
"item.extendedae_plus.network_pattern_controller": "Pattern Provider Status Controller",
"item.extendedae_plus.assembler_matrix_upload_core": "Assembly Matrix Upload Core",
+ "item.extendedae_plus.channel_card": "Channel Card",
+ "item.extendedae_plus.channel_card.channel": "Channel: %d",
+ "item.extendedae_plus.channel_card.channel.unset": "Channel: Unset",
+ "item.extendedae_plus.channel_card.set": "Channel set to: %d",
"item.extendedae_plus.entity_speed_card": "Entity Speed Card",
"item.extendedae_plus.entity_speed_card.x2": "Entity Speed Card (x2)",
"item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
index b026489..e7cb381 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -18,6 +18,10 @@
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
"item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
"item.extendedae_plus.assembler_matrix_upload_core": "装配矩阵上传核心",
+ "item.extendedae_plus.channel_card": "频道卡",
+ "item.extendedae_plus.channel_card.channel": "频道: %d",
+ "item.extendedae_plus.channel_card.channel.unset": "频道: 未设置",
+ "item.extendedae_plus.channel_card.set": "频道已设置为: %d",
"item.extendedae_plus.entity_speed_card": "实体加速卡",
"item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
diff --git a/src/main/resources/assets/extendedae_plus/models/item/channel_card.json b/src/main/resources/assets/extendedae_plus/models/item/channel_card.json
new file mode 100644
index 0000000..8699938
--- /dev/null
+++ b/src/main/resources/assets/extendedae_plus/models/item/channel_card.json
@@ -0,0 +1,6 @@
+{
+ "parent": "item/generated",
+ "textures": {
+ "layer0": "extendedae_plus:item/channel_card"
+ }
+}
diff --git a/src/main/resources/data/extendedae_plus/recipes/channel_card.json b/src/main/resources/data/extendedae_plus/recipes/channel_card.json
new file mode 100644
index 0000000..d6b23ce
--- /dev/null
+++ b/src/main/resources/data/extendedae_plus/recipes/channel_card.json
@@ -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 }
+}
diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json
index 034dc6d..1e725ba 100644
--- a/src/main/resources/extendedae_plus.mixins.json
+++ b/src/main/resources/extendedae_plus.mixins.json
@@ -25,8 +25,11 @@
"ae2.autopattern.CraftingTreeNodeMixin",
"ae2.autopattern.CraftingTreeProcessMixin",
"ae2.autopattern.PatternProviderLogicContainsRedirectMixin",
+ "ae2.helpers.InterfaceLogicChannelCardMixin",
"ae2.helpers.PatternProviderLogicAdvancedMixin",
"ae2.helpers.PatternProviderLogicDoublingMixin",
+ "ae2.parts.automation.IOBusPartChannelCardMixin",
+ "ae2.parts.storagebus.StorageBusPartChannelCardMixin",
"ae2.menu.ContainerPatternEncodingTermMenuMixin",
"ae2.menu.MEStorageMenuMixin",
"ae2.menu.PatternEncodingTermMenuMixin",
From 56dcdad91f93bc8b265a04e2ec9dbd531b76d807 Mon Sep 17 00:00:00 2001
From: GaLicn <133291877+GaLicn@users.noreply.github.com>
Date: Wed, 24 Sep 2025 15:10:43 +0800
Subject: [PATCH 17/43] =?UTF-8?q?=E9=A2=91=E9=81=93=E5=8D=A1=E9=80=BB?=
=?UTF-8?q?=E8=BE=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../extendedae_plus/init/UpgradeCards.java | 20 +++++++++
.../helpers/InterfaceLogicTickerMixin.java | 42 +++++++++++++++++++
.../resources/extendedae_plus.mixins.json | 1 +
3 files changed, 63 insertions(+)
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java
diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java
index 9a8de67..0816756 100644
--- a/src/main/java/com/extendedae_plus/init/UpgradeCards.java
+++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java
@@ -1,7 +1,10 @@
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.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
@@ -12,6 +15,23 @@ 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");
+
+ // 频道卡:对齐旧版注册 — AE2 接口(方块与部件)
+ 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);
+
+ // 频道卡:AE2 样板供应器(方块与部件)
+ 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);
+
+ // 频道卡:AE2 I/O 总线与存储总线(部件)
+ 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);
});
}
}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java
new file mode 100644
index 0000000..86f561c
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java
@@ -0,0 +1,42 @@
+package com.extendedae_plus.mixin.ae2.helpers;
+
+import appeng.helpers.InterfaceLogic;
+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;
+
+/**
+ * 注入到 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 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 cir) {
+ if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
+ bridge.eap$updateWirelessLink();
+ }
+ }
+}
diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json
index 1e725ba..b562441 100644
--- a/src/main/resources/extendedae_plus.mixins.json
+++ b/src/main/resources/extendedae_plus.mixins.json
@@ -26,6 +26,7 @@
"ae2.autopattern.CraftingTreeProcessMixin",
"ae2.autopattern.PatternProviderLogicContainsRedirectMixin",
"ae2.helpers.InterfaceLogicChannelCardMixin",
+ "ae2.helpers.InterfaceLogicTickerMixin",
"ae2.helpers.PatternProviderLogicAdvancedMixin",
"ae2.helpers.PatternProviderLogicDoublingMixin",
"ae2.parts.automation.IOBusPartChannelCardMixin",
From 5bd5df3468ef702c20648f87610c91361b68f2ca Mon Sep 17 00:00:00 2001
From: GaLicn <133291877+GaLicn@users.noreply.github.com>
Date: Wed, 24 Sep 2025 15:26:18 +0800
Subject: [PATCH 18/43] =?UTF-8?q?me=E6=8E=A5=E5=8F=A3=E9=A2=91=E9=81=93?=
=?UTF-8?q?=E5=8D=A1=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../InterfaceLogicChannelCardMixin.java | 30 +++++++++++++++++--
.../wireless/WirelessSlaveLink.java | 16 +++++++++-
2 files changed, 42 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
index 15fd6f3..41eb689 100644
--- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java
@@ -8,6 +8,7 @@ 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 com.extendedae_plus.util.ExtendedAELogger;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
@@ -50,6 +51,7 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
@Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false)
private void eap$onUpgradesChangedTail(CallbackInfo ci) {
// 升级变更时重置标志并尝试初始化
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface onUpgradesChanged -> 重置初始化状态");
eap$lastChannel = -1;
eap$hasInitialized = false;
eap$initializeChannelLink();
@@ -58,6 +60,7 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
@Inject(method = "gridChanged", at = @At("TAIL"), remap = false)
private void eap$afterGridChanged(CallbackInfo ci) {
// 网格状态变化时重置标志并设置延迟初始化
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface gridChanged -> 延迟初始化计时开始");
eap$lastChannel = -1;
eap$hasInitialized = false;
eap$delayedInitTicks = 10; // 适当增加延迟tick,等待网格完成引导
@@ -100,8 +103,9 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
return;
}
- // 检查网格节点是否在线(1.21.1版本使用isActive替代hasGridBooted)
- if (mainNode == null || !mainNode.isActive()) {
+ // 仅要求节点对象可用;不要依赖 isActive(无线连接本身会建立连接与激活节点)
+ if (mainNode == null || mainNode.getNode() == null) {
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface 初始化频道链接跳过:mainNode 或其 Node 不可用");
return;
}
@@ -121,18 +125,36 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
if (eap$link != null) {
eap$link.setFrequency(0L);
eap$link.updateStatus();
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface 无频道卡 -> 断开无线链接");
}
eap$hasInitialized = true;
+ // 保存一次状态
+ try { host.saveChanges(); } catch (Throwable ignored) {}
+ // 唤醒设备以刷新客户端/邻居
+ try {
+ mainNode.ifPresent((grid, node) -> {
+ try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored2) {}
+ });
+ } catch (Throwable ignored2) {}
return;
}
if (eap$link == null) {
var endpoint = new InterfaceNodeEndpointImpl(host, () -> this.mainNode.getNode());
eap$link = new WirelessSlaveLink(endpoint);
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface 创建新的无线链接实例");
}
eap$link.setFrequency(channel);
eap$link.updateStatus();
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface 设置频道: {} 连接状态: {}", channel, eap$link.isConnected());
+ try { host.saveChanges(); } catch (Throwable ignored) {}
+ // 唤醒设备,加速后续 tick 以完成连接
+ try {
+ mainNode.ifPresent((grid, node) -> {
+ try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored2) {}
+ });
+ } catch (Throwable ignored2) {}
if (eap$link.isConnected()) {
eap$hasInitialized = true; // 设置初始化完成标志
@@ -196,7 +218,8 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
// 若尚未初始化,则持续尝试,直到网格完成引导
if (!eap$hasInitialized) {
- if (mainNode == null || !mainNode.isActive()) {
+ // 若节点对象尚未就绪,则等待;无需等待 isActive(无线接入后会激活)
+ if (mainNode == null || mainNode.getNode() == null) {
// 仍在引导,消耗计时器
if (eap$delayedInitTicks > 0) {
eap$delayedInitTicks--;
@@ -216,6 +239,7 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
}
} else {
// 网格已引导完成,执行初始化
+ ExtendedAELogger.LOGGER.debug("[服务端] Interface 延迟初始化触发 -> 开始初始化频道链接");
eap$initializeChannelLink();
}
}
diff --git a/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java b/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java
index cc8988a..134d014 100644
--- a/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java
+++ b/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java
@@ -4,6 +4,7 @@ import appeng.api.networking.GridHelper;
import appeng.api.networking.IGridNode;
import appeng.me.service.helpers.ConnectionWrapper;
import com.extendedae_plus.config.ModConfigs;
+import com.extendedae_plus.util.ExtendedAELogger;
import net.minecraft.server.level.ServerLevel;
import java.util.Objects;
@@ -51,16 +52,19 @@ public class WirelessSlaveLink {
*/
public void updateStatus() {
if (host.isEndpointRemoved()) {
+ ExtendedAELogger.LOGGER.debug("[无线] 端点已移除或无效,销毁连接");
destroyConnection();
return;
}
final ServerLevel level = host.getServerLevel();
if (level == null || frequency == 0L) {
+ ExtendedAELogger.LOGGER.debug("[无线] 环境不满足:level={}, freq={}", level, frequency);
destroyConnection();
return;
}
IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency);
+ ExtendedAELogger.LOGGER.debug("[无线] 查找主站: level={}, freq={} -> {}", level.dimension(), frequency, master);
shutdown = false;
distance = 0.0D;
@@ -68,6 +72,7 @@ public class WirelessSlaveLink {
if (master != null && !master.isEndpointRemoved() && (crossDim || master.getServerLevel() == level)) {
if (!crossDim) {
distance = Math.sqrt(master.getBlockPos().distSqr(host.getBlockPos()));
+ ExtendedAELogger.LOGGER.debug("[无线] 同维度距离={}, 最大距离={}", distance, ModConfigs.WIRELESS_MAX_RANGE.get());
}
double maxRange = ModConfigs.WIRELESS_MAX_RANGE.get();
if (crossDim || distance <= maxRange) {
@@ -76,6 +81,8 @@ public class WirelessSlaveLink {
var current = connection.getConnection();
IGridNode a = host.getGridNode(); // 从端
IGridNode b = master.getGridNode(); // 主端
+ if (a == null) { ExtendedAELogger.LOGGER.debug("[无线] 从端节点为 null,无法连接"); }
+ if (b == null) { ExtendedAELogger.LOGGER.debug("[无线] 主端节点为 null,无法连接"); }
if (a == null || b == null) {
shutdown = true;
} else {
@@ -84,22 +91,29 @@ public class WirelessSlaveLink {
var ca = current.a();
var cb = current.b();
if ((ca == a || cb == a) && (ca == b || cb == b)) {
+ ExtendedAELogger.LOGGER.debug("[无线] 连接已存在且目标一致,保持");
return; // 连接已正确
}
// 否则先断开,再重建
+ ExtendedAELogger.LOGGER.debug("[无线] 连接目标变化,先销毁再重建");
current.destroy();
connection = new ConnectionWrapper(null);
}
+ ExtendedAELogger.LOGGER.debug("[无线] 创建连接: a={}, b={}", a, b);
connection = new ConnectionWrapper(GridHelper.createConnection(a, b));
+ ExtendedAELogger.LOGGER.debug("[无线] 连接创建完成: {}", connection.getConnection());
return;
}
- } catch (IllegalStateException ignore) {
+ } catch (IllegalStateException ex) {
// 连接非法(如重复连接等)——落入重建/关闭逻辑
+ ExtendedAELogger.LOGGER.debug("[无线] 连接创建异常: {}", ex.toString());
}
} else {
+ ExtendedAELogger.LOGGER.debug("[无线] 超出范围:{} > {},关闭连接", distance, maxRange);
shutdown = true; // 超出范围
}
} else {
+ ExtendedAELogger.LOGGER.debug("[无线] 无可用主站或跨维度不允许,关闭连接");
shutdown = true; // 无主或主端不可用
}
From 5cf2aa991f7c5452cfe8555cc85ea6708d832ff6 Mon Sep 17 00:00:00 2001
From: GaLicn <133291877+GaLicn@users.noreply.github.com>
Date: Wed, 24 Sep 2025 20:52:35 +0800
Subject: [PATCH 19/43] =?UTF-8?q?=E6=A0=B7=E6=9D=BF=E4=BE=9B=E5=BA=94?=
=?UTF-8?q?=E5=99=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../bridge/CompatUpgradeProvider.java | 10 +
.../bridge/IUpgradableMenu.java | 12 +
.../compat/UpgradeSlotCompat.java | 38 +++
.../PatternProviderScreenUpgradesMixin.java | 67 +++++
.../PatternProviderLogicCompatMixin.java | 280 ++++++++++++++++++
.../PatternProviderLogicTickerMixin.java | 42 +++
.../PatternProviderMenuUpgradesMixin.java | 54 ++++
.../resources/extendedae_plus.mixins.json | 3 +
8 files changed, 506 insertions(+)
create mode 100644 src/main/java/com/extendedae_plus/bridge/CompatUpgradeProvider.java
create mode 100644 src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java
create mode 100644 src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java
create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java
diff --git a/src/main/java/com/extendedae_plus/bridge/CompatUpgradeProvider.java b/src/main/java/com/extendedae_plus/bridge/CompatUpgradeProvider.java
new file mode 100644
index 0000000..a221c7e
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/bridge/CompatUpgradeProvider.java
@@ -0,0 +1,10 @@
+package com.extendedae_plus.bridge;
+
+import appeng.api.upgrades.IUpgradeInventory;
+
+/**
+ * 仅用于我方在未安装 appflux 时向逻辑类暴露自带升级槽,避免与 appflux 的 IUpgradeableObject 冲突。
+ */
+public interface CompatUpgradeProvider {
+ IUpgradeInventory eap$getCompatUpgrades();
+}
diff --git a/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java b/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java
new file mode 100644
index 0000000..9599172
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java
@@ -0,0 +1,12 @@
+package com.extendedae_plus.bridge;
+
+import appeng.menu.ToolboxMenu;
+
+/**
+ * 提供给 AE2 菜单以暴露升级槽与工具箱面板的接口。
+ * PatternProviderMenu 的 mixin 会在未安装 appflux 时实现该接口,
+ * 以便在界面上显示升级卡槽(用于放置频道卡)。
+ */
+public interface IUpgradableMenu {
+ ToolboxMenu getToolbox();
+}
diff --git a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java
new file mode 100644
index 0000000..0c9f191
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java
@@ -0,0 +1,38 @@
+package com.extendedae_plus.compat;
+
+import net.neoforged.fml.loading.FMLLoader;
+import net.neoforged.fml.ModList;
+
+public final class UpgradeSlotCompat {
+ private static Boolean APPFLUX_LOADED;
+
+ private UpgradeSlotCompat() {}
+
+ private static boolean isAppfluxLoaded() {
+ if (APPFLUX_LOADED == null) {
+ try {
+ APPFLUX_LOADED = ModList.get().isLoaded("appflux");
+ } catch (Throwable t) {
+ // 早期阶段或运行环境差异
+ APPFLUX_LOADED = false;
+ }
+ }
+ return APPFLUX_LOADED;
+ }
+
+ // 是否由我们提供升级槽(当未安装 appflux 时)
+ public static boolean shouldEnableUpgradeSlots() {
+ return !isAppfluxLoaded();
+ }
+
+ // 是否启用频道卡支持(两种情况下都启用)
+ public static boolean shouldEnableChannelCard() {
+ return true;
+ }
+
+ // 客户端界面是否需要显示升级面板(装/不装 appflux 均显示;
+ // appflux 提供的升级槽会以 SlotSemantics.UPGRADE 出现在菜单中,我们只负责渲染面板)
+ public static boolean shouldAddUpgradePanelToScreen() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java
new file mode 100644
index 0000000..d8b60fb
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java
@@ -0,0 +1,67 @@
+package com.extendedae_plus.mixin.ae2.client.gui;
+
+import appeng.api.upgrades.Upgrades;
+import appeng.client.gui.AEBaseScreen;
+import appeng.client.gui.implementations.PatternProviderScreen;
+import appeng.client.gui.style.ScreenStyle;
+import appeng.client.gui.widgets.ToolboxPanel;
+import appeng.client.gui.widgets.UpgradesPanel;
+import appeng.core.localization.GuiText;
+import appeng.menu.SlotSemantics;
+import appeng.menu.implementations.PatternProviderMenu;
+import appeng.helpers.patternprovider.PatternProviderLogicHost;
+import appeng.menu.AEBaseMenu;
+import com.extendedae_plus.compat.UpgradeSlotCompat;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.entity.player.Inventory;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mixin(value = PatternProviderScreen.class, priority = 2000, remap = false)
+public abstract class PatternProviderScreenUpgradesMixin extends AEBaseScreen {
+
+ @Inject(method = "", at = @At("TAIL"))
+ private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
+ if (!UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) {
+ return;
+ }
+ try {
+ this.widgets.add("upgrades", new UpgradesPanel(
+ menu.getSlots(SlotSemantics.UPGRADE),
+ this::eap$getCompatibleUpgrades));
+ } catch (IllegalStateException already) {
+ // 已存在同名面板(可能由 AE2 或其他模组添加),忽略
+ com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("[样板供应器][界面] 升级面板已存在,跳过添加");
+ }
+ if (menu instanceof AEBaseMenu base && base instanceof com.extendedae_plus.bridge.IUpgradableMenu upg && upg.getToolbox() != null && upg.getToolbox().isPresent()) {
+ try {
+ this.widgets.add("toolbox", new ToolboxPanel(style, upg.getToolbox().getName()));
+ } catch (IllegalStateException already) {
+ com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("[样板供应器][界面] 工具箱面板已存在,跳过添加");
+ }
+ }
+ }
+
+ @Unique
+ private List eap$getCompatibleUpgrades() {
+ var list = new ArrayList