Merge remote-tracking branch 'origin/develop/InfinityBigIntCell'
This commit is contained in:
commit
f53089601a
47
CHANGELOG.md
47
CHANGELOG.md
|
|
@ -2,6 +2,53 @@
|
|||
|
||||
## [Unreleased] / [未发布]
|
||||
|
||||
## [1.4.2] / [未发布]
|
||||
### Added / 新增
|
||||
- 添加实体加速器,最高可加速 1024 倍(配置文件可配置能耗、黑名单、额外消耗倍率名单)
|
||||
- Added Entity Accelerator block, capable of up to 1024× acceleration (configurable energy cost, blacklist, and extra consumption multiplier list).
|
||||
- 添加实体加速卡系列,用于设置实体加速器加速倍率
|
||||
- Added Entity Acceleration Card series to configure acceleration multiplier for the Entity Accelerator.
|
||||
- 添加物品“吞噬万籁的寂静”,可存储 21 亿种不同的资源,每种资源存储数量无上限(气体等物品须安装对应附属 mod)
|
||||
- Added item “Devourer of Cosmic Silence”: stores up to 2.1 billion distinct resources with unlimited quantity per type (gases and other types require respective addon mods).
|
||||
- eae扩展样板管理界面添加F键搜索支持
|
||||
- Added F-key search support in Extended Pattern Management Terminal.
|
||||
- 合成计划界面支持 Shift+点击取消自动添加缺失材料至 JEI 书签
|
||||
- Crafting Plan GUI: Shift-clicking cancel button auto-adds missing ingredients to JEI bookmarks.
|
||||
- AE 合成暂停检查阈值配置项(默认值 100000)
|
||||
- Added config option for AE crafting pause threshold (default: 100000).
|
||||
- 智能系列支持高级 AE 供应器
|
||||
- Smart series now supports Advanced AE Providers.
|
||||
- 无线收发器支持重命名,Jade 可以在从节点显示主节点名称
|
||||
- Wireless Repeater supports renaming; Jade HUD displays master node name on slave terminals.
|
||||
- 智能阻挡开启时自动启用原版阻挡
|
||||
- Enabling Smart Blocking now also activates Vanilla Blocking automatically.
|
||||
- 调整样板制作数量显示上限
|
||||
- Adjusted display limit for pattern output quantity.
|
||||
- 放宽扩展供应器样板手动倍增限制
|
||||
- Relaxed manual doubling restrictions for Extended Providers.
|
||||
### Fixed / 修复
|
||||
- 修复无线收发器频道限制与渲染问题
|
||||
- Fixed channel limit and rendering issues for Wireless Repeater.
|
||||
|
||||
## [1.4.1]
|
||||
### Added / 新增
|
||||
- 模组配置项:可设置智能倍增的最大倍数
|
||||
- Config option: set maximum multiplier for Smart Doubling.
|
||||
- 上传样板搜索框:右键点击可清空文本
|
||||
- Right-clicking the pattern upload search box clears the text.
|
||||
- 合成监控界面:打开样板供应器 UI 时自动跳转到该样板所在页数,并以彩虹高亮显示
|
||||
- Crafting monitor: auto-jumps to the page containing the pattern and highlights it with rainbow effect when opening the provider UI.
|
||||
- 样板供应器 UI 标题现在显示为玩家自定义名称
|
||||
- Pattern Provider UI now displays the player-customized name as its title.
|
||||
- 配置项:当产物数量达到指定值时启用智能倍增
|
||||
- Config option: enable Smart Doubling only when output amount reaches a specified threshold.
|
||||
|
||||
### Fixed / 修复
|
||||
- 修复编码终端中空白配方无法覆盖已有编码样板的问题
|
||||
- Fixed issue where blank recipes couldn't overwrite existing encoded patterns in the Encoding Terminal.
|
||||
- 修复非 AE 与 ExtendedAE 样板供应器无法发配材料的问题
|
||||
- Fixed issue where non-AE2 and non-ExtendedAE pattern providers failed to dispatch materials.
|
||||
|
||||
## [1.4.0-fix]
|
||||
### Added / 新增
|
||||
- JEI 书签优先用于编码样板匹配(书签越靠前,匹配优先级越高)
|
||||
|
|
|
|||
|
|
@ -42,5 +42,6 @@ ExtendedAE Plus 是一个面向 Applied Energistics 2 与 ExtendedAE 的功能
|
|||
- Applied Energistics 2(AE2):MIT License
|
||||
- SpongePowered Mixin:MIT License
|
||||
- Configuration(by Toma):MIT License
|
||||
- AE2Things(by ProjectET):MIT License
|
||||
|
||||
请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
|
|||
loom.platform = forge
|
||||
|
||||
# Mod properties
|
||||
mod_version = 1.4.1-entitySpeedTicker
|
||||
mod_version = 1.4.2-beta
|
||||
maven_group = com.extendedae_plus
|
||||
archives_name = extendedae_plus
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
package com.extendedae_plus;
|
||||
|
||||
import appeng.api.storage.StorageCells;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||
import com.extendedae_plus.client.ClientRegistrar;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.*;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ModelEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.level.LevelEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
|
@ -46,8 +52,12 @@ public class ExtendedAEPlus {
|
|||
|
||||
// 注册到Forge事件总线
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
|
||||
// 注册通用配置
|
||||
ModConfig.init();
|
||||
// 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush)
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
|
||||
// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +65,8 @@ public class ExtendedAEPlus {
|
|||
* 通用初始化设置
|
||||
*/
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
|
||||
|
||||
// 注册本模组网络通道与数据包
|
||||
event.enqueueWork(() -> {
|
||||
// 注册升级卡
|
||||
|
|
@ -104,4 +116,11 @@ public class ExtendedAEPlus {
|
|||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// 在世界加载时注册/加载 SavedData
|
||||
private static void onLevelLoad(LevelEvent.Load event) {
|
||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
||||
InfinityStorageManager.getForLevel(serverLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,413 @@
|
|||
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.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
/**
|
||||
* InfinityBigIntegerCellInventory
|
||||
* <p>
|
||||
* 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。
|
||||
* 主要职责:
|
||||
* - 在内存中维护条目映射 (AEKey -> BigInteger 数量)
|
||||
* - 提供插入/提取/列举/持久化等操作的实现
|
||||
* - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化
|
||||
* <p>
|
||||
* 重要字段:
|
||||
* - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息
|
||||
* - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化
|
||||
* - storedMap: 延迟初始化的内存映射,减少未使用时内存占用
|
||||
* - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描
|
||||
* - isPersisted: 标记内存状态是否已同步到持久层
|
||||
*/
|
||||
public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||
|
||||
// 待持久化队列(用于 debounce:在服务器 tick 中合并持久化)
|
||||
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
|
||||
// 数字格式化对象,保留两位小数(复用以减少对象分配)
|
||||
private static final DecimalFormat DF = new DecimalFormat("#.##");
|
||||
|
||||
// 关联的 ItemStack(含可能的 uuid NBT)
|
||||
private final ItemStack stack;
|
||||
// AE2 提供的保存提供者,用于在容器中批量保存时触发回调
|
||||
private final ISaveProvider container;
|
||||
// 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化)
|
||||
private Object2ObjectMap<AEKey, BigInteger> 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(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
// 处理本次 tick 中的全部待持久化项
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在服务器停止时被调用,立即强制持久化队列中的所有实例
|
||||
public static void onServerStopping(ServerStoppingEvent event) {
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发)
|
||||
try {
|
||||
var stor = getStorageInstance();
|
||||
if (stor != null) stor.setDirty();
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 将 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() {
|
||||
return stack.hasTag() && stack.getOrCreateTag().contains("uuid");
|
||||
}
|
||||
|
||||
// 获取物品堆栈的UUID
|
||||
public UUID getUUID() {
|
||||
if (this.hasUUID())
|
||||
return stack.getOrCreateTag().getUUID("uuid");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取或初始化存储映射
|
||||
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
|
||||
if (storedMap == null) {
|
||||
storedMap = new Object2ObjectOpenHashMap<>();
|
||||
this.loadCellStoredMap();
|
||||
}
|
||||
return storedMap;
|
||||
}
|
||||
|
||||
// 从存储中加载物品映射
|
||||
private void loadCellStoredMap() {
|
||||
boolean corruptedTag = false; // 标记数据是否损坏
|
||||
if (!stack.hasTag()) return;
|
||||
ListTag keys = this.getCellStorage().keys;
|
||||
ListTag amounts = this.getCellStorage().amounts;
|
||||
int len = Math.min(keys.size(), amounts.size());
|
||||
for (int i = 0; i < len; i++) {
|
||||
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
|
||||
CompoundTag amtTag = amounts.getCompound(i);
|
||||
try {
|
||||
BigInteger amount;
|
||||
if (amtTag.contains("l")) {
|
||||
long v = amtTag.getLong("l");
|
||||
amount = BigInteger.valueOf(v);
|
||||
} else if (amtTag.contains("s")) {
|
||||
amount = new BigInteger(amtTag.getString("s"));
|
||||
} else {
|
||||
corruptedTag = true;
|
||||
continue;
|
||||
}
|
||||
if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
|
||||
corruptedTag = true;
|
||||
} else {
|
||||
// storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销
|
||||
storedMap.put(key, amount);
|
||||
// 更新缓存的总数
|
||||
totalStored = totalStored.add(amount);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
corruptedTag = true;
|
||||
}
|
||||
}
|
||||
// 如果有损坏,保存修正后的数据
|
||||
if (corruptedTag) {
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// 标记数据需要保存,并通知容器或直接持久化
|
||||
private void saveChanges() {
|
||||
// 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O
|
||||
isPersisted = false;
|
||||
if (container != null) {
|
||||
// 当存在容器时,优先让容器统一处理持久化
|
||||
container.saveChanges();
|
||||
} else {
|
||||
// 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O
|
||||
if (!PENDING_PERSIST.contains(this)) {
|
||||
PENDING_PERSIST.offer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有可用的物品堆栈及其数量
|
||||
@Override
|
||||
public void getAvailableStacks(KeyCounter out) {
|
||||
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
Object2ObjectMap<AEKey, BigInteger> map = getCellStoredMap();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
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<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
if (map.isEmpty()) {
|
||||
// 如果存储为空,移除UUID和全局存储中的数据
|
||||
if (this.hasUUID()) {
|
||||
getStorageInstance().removeCell(getUUID());
|
||||
if (stack.getTag() != null) {
|
||||
stack.getTag().remove("uuid");
|
||||
// 移除缓存的 total 字段
|
||||
stack.getTag().remove("total");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 构建要保存的Key和数量列表(混合表示:long 或 string)
|
||||
ListTag amountTags = new ListTag();
|
||||
ListTag keys = new ListTag();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
BigInteger amount = entry.getValue();
|
||||
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
||||
keys.add(entry.getKey().toTagGeneric());
|
||||
CompoundTag amt = new CompoundTag();
|
||||
if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
amt.putLong("l", amount.longValue());
|
||||
} else {
|
||||
amt.putString("s", amount.toString());
|
||||
}
|
||||
amountTags.add(amt);
|
||||
}
|
||||
}
|
||||
// 如果没有Key,更新为空存储,否则保存数据
|
||||
if (keys.isEmpty()) {
|
||||
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
|
||||
} else {
|
||||
// amounts 现在为 CompoundTag 列表
|
||||
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags);
|
||||
}
|
||||
// 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long
|
||||
if (stack.getOrCreateTag() != null) {
|
||||
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
stack.getOrCreateTag().putLong("total", totalStored.longValue());
|
||||
} else {
|
||||
stack.getOrCreateTag().putString("total", totalStored.toString());
|
||||
}
|
||||
// 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示
|
||||
int typesCount = this.getCellStoredMap().size();
|
||||
stack.getOrCreateTag().putInt("types", typesCount);
|
||||
}
|
||||
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()) {
|
||||
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
|
||||
getStorageInstance().getOrCreateCell(getUUID());
|
||||
// 确保 storedMap 初始化并从持久层加载数据
|
||||
this.getCellStoredMap();
|
||||
}
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
||||
if (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<AEKey, BigInteger> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package com.extendedae_plus.ae.client.gui;
|
||||
|
||||
import appeng.client.gui.style.Blitter;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public enum Icon {
|
||||
REDSTONE_LOW(0, 0),
|
||||
REDSTONE_HIGH(16, 0);
|
||||
|
||||
public final int x;
|
||||
public final int y;
|
||||
public final int width;
|
||||
public final int height;
|
||||
public static final ResourceLocation TEXTURE = new ResourceLocation(ExtendedAEPlus.MODID, "textures/guis/states.png");
|
||||
public static final int TEXTURE_WIDTH = 256;
|
||||
public static final int TEXTURE_HEIGHT = 256;
|
||||
|
||||
private Icon(int x, int y) {
|
||||
this(x, y, 16, 16);
|
||||
}
|
||||
|
||||
private Icon(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Blitter getBlitter() {
|
||||
return Blitter.texture(TEXTURE, TEXTURE_WIDTH, TEXTURE_HEIGHT).src(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
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.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.level.Level;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
public class InfinityBigIntegerCellItem extends Item {
|
||||
|
||||
public InfinityBigIntegerCellItem() {
|
||||
super(new Properties().stacksTo(1).fireResistant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在物品悬停提示中展示额外信息。
|
||||
* 功能:
|
||||
* - 若 ItemStack 的 NBT 含有 UUID,则显示该 UUID(不会触发服务器加载或持久化行为)
|
||||
* - 若 NBT 同步了 total 字段,则读取并格式化显示总存储量(使用 Inventory 的 formatBigInteger)
|
||||
*
|
||||
* 设计说明:客户端 tooltip 不主动访问服务端 SavedData,以避免不必要的 I/O 与状态变更。
|
||||
*/
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack,
|
||||
@Nullable Level world,
|
||||
@NotNull List<Component> tooltip,
|
||||
@NotNull TooltipFlag context) {
|
||||
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);
|
||||
// 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
|
||||
CompoundTag tag = stack.getTag();
|
||||
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))
|
||||
);
|
||||
// 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey
|
||||
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) {
|
||||
// ignore malformed value
|
||||
}
|
||||
}
|
||||
// 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位
|
||||
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) {
|
||||
// 解析失败保持为 0
|
||||
}
|
||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||
tooltip.add(
|
||||
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,8 @@ public final class ModCreativeTabs {
|
|||
output.accept(ModItems.createEntitySpeedCardStack(4));
|
||||
output.accept(ModItems.createEntitySpeedCardStack(8));
|
||||
output.accept(ModItems.createEntitySpeedCardStack(16));
|
||||
|
||||
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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 com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
|
|
@ -65,6 +66,11 @@ public final class ModItems {
|
|||
() -> new EntitySpeedCardItem(new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
|
||||
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* 为 PartItem 注册 AE2 部件模型。
|
||||
* 在客户端进行模型/几何体注册时调用。
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<IPatternDetails> eap$patterns();
|
||||
}
|
||||
|
|
@ -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("logic")
|
||||
AdvPatternProviderLogic eap$logic();
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
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.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
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<AdvPatternProviderMenu> {
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$AdvancedBlockingEnabled = false;
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> 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 = "<init>", 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<YesNo> 的外观(原版图标),但自定义悬停描述为“智能阻挡”
|
||||
this.eap$AdvancedBlockingToggle = new SettingToggleButton<>(
|
||||
Settings.BLOCKING_MODE,
|
||||
this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<Component> 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) -> {
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<Component> 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);
|
||||
}
|
||||
|
||||
if ((Object) this instanceof GuiExPatternProvider) {
|
||||
try {
|
||||
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
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.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
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<SmallAdvPatternProviderMenu> {
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$AdvancedBlockingEnabled = false;
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> 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 = "<init>", 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<YesNo> 的外观(原版图标),但自定义悬停描述为“智能阻挡”
|
||||
this.eap$AdvancedBlockingToggle = new SettingToggleButton<>(
|
||||
Settings.BLOCKING_MODE,
|
||||
this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<Component> 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) -> {
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<Component> 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);
|
||||
}
|
||||
|
||||
if ((Object) this instanceof GuiExPatternProvider) {
|
||||
try {
|
||||
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
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.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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, CallbackInfo ci) {
|
||||
tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readAdvancedFromNbt(CompoundTag tag, 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<AEKey> 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() {}
|
||||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = input.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
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.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
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, CallbackInfo ci) {
|
||||
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readSmartDoublingFromNbt(CompoundTag tag, 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() {}
|
||||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = input.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
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.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogicHost;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
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 {
|
||||
@Shadow
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造器尾注入(public ctor)
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 构造器尾注入(protected ctor with MenuType)
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
private void eap$initAdvancedSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$getAdvancedBlockingSynced() {
|
||||
return this.eap$AdvancedBlocking;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
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.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogicHost;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
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 {
|
||||
@Shadow
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
private void eap$initSmartSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
private void eap$initSmartSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$getSmartDoublingSynced() {
|
||||
return this.eap$SmartDoubling;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,19 +3,13 @@ package com.extendedae_plus.mixin.ae2.client.gui;
|
|||
import appeng.client.gui.implementations.PatternProviderScreen;
|
||||
import com.extendedae_plus.content.ClientPatternHighlightStore;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = AbstractContainerScreen.class, remap = false)
|
||||
@Mixin(value = AbstractContainerScreen.class)
|
||||
public class PatternProviderCloseMixin {
|
||||
|
||||
@Shadow
|
||||
protected AbstractContainerMenu menu;
|
||||
|
||||
@Inject(method = "removed", at = @At("HEAD"))
|
||||
private void onRemoved(CallbackInfo ci) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
LOGGER.debug("[EAP] Click advanced blocking toggle: send C2S");
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket());
|
||||
}
|
||||
) {
|
||||
|
|
@ -80,7 +79,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
}
|
||||
};
|
||||
// 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
|
||||
LOGGER.debug("[EAP] Screen init: initial synced={} -> set button", this.eap$AdvancedBlockingEnabled);
|
||||
this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
|
||||
|
||||
this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
|
||||
|
|
@ -98,7 +96,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
Settings.BLOCKING_MODE,
|
||||
this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
LOGGER.debug("[EAP] Click smart doubling toggle: send C2S");
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
|
|
@ -125,7 +122,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (adv): desired={}", desired);
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
|
@ -135,7 +131,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
|
||||
desired2 = sync2.eap$getSmartDoublingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (dbl): desired={}", desired2);
|
||||
this.eap$SmartDoublingEnabled = desired2;
|
||||
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,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 = "eap_advanced_blocking";
|
||||
|
||||
@Unique
|
||||
private boolean eap$advancedBlocking = false;
|
||||
|
|
@ -39,13 +39,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
|||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeAdvancedToNbt(CompoundTag tag, 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,13 +95,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
|||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean("eap_advanced_blocking", this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains("eap_advanced_blocking")) {
|
||||
this.eap$advancedBlocking = input.getBoolean("eap_advanced_blocking");
|
||||
if (input.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = input.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,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 = "eap_smart_doubling";
|
||||
|
||||
@Unique
|
||||
private boolean eap$smartDoubling = false;
|
||||
|
|
@ -47,13 +47,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
|||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeSmartDoublingToNbt(CompoundTag tag, 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,13 +77,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
|||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean("eap_smart_doubling", this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains("eap_smart_doubling")) {
|
||||
this.eap$smartDoubling = input.getBoolean("eap_smart_doubling");
|
||||
if (input.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = input.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ 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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
|
|
@ -36,7 +35,6 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
|
|||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
LOGGER.debug("[EAP] Menu broadcastChanges HEAD: eap$AdvancedBlocking={}", this.eap$AdvancedBlocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,9 +47,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
|
|||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing advanced sync", t);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 构造器尾注入(protected ctor with MenuType)
|
||||
|
|
@ -71,13 +67,4 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
|
|||
public boolean eap$getAdvancedBlockingSynced() {
|
||||
return this.eap$AdvancedBlocking;
|
||||
}
|
||||
|
||||
// 调试:当 Screen 每帧读取这些 getter 时打印,验证 Mixin 是否生效
|
||||
@Inject(method = "getBlockingMode", at = @At("HEAD"), remap = false)
|
||||
private void eap$debug_getBlockingMode(CallbackInfoReturnable<?> cir) {
|
||||
}
|
||||
|
||||
@Inject(method = "getShowInAccessTerminal", at = @At("HEAD"), remap = false)
|
||||
private void eap$debug_getShowInAccessTerminal(CallbackInfoReturnable<?> cir) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
|
|||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
LOGGER.debug("[EAP] Menu broadcastChanges HEAD: eap$SmartDoubling={}", this.eap$SmartDoubling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ 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.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -29,19 +31,32 @@ public class ToggleAdvancedBlockingC2SPacket {
|
|||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
||||
|
||||
// 通过 accessor 获取逻辑与当前状态
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package com.extendedae_plus.network;
|
|||
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
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.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -27,15 +29,25 @@ public class ToggleSmartDoublingC2SPacket {
|
|||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.extendedae_plus.util.storage;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* InfinityStorageManager
|
||||
* <p>
|
||||
* 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
|
||||
* 功能要点:
|
||||
* - 在世界加载时从存档恢复所有 cell 的数据
|
||||
* - 提供按 UUID 获取/创建单个 cell 的数据容器
|
||||
* - 在世界保存时将内存数据打包为 NBT 写回存档
|
||||
*/
|
||||
public class InfinityStorageManager extends SavedData {
|
||||
|
||||
/**
|
||||
* SavedData 文件名常量
|
||||
*/
|
||||
public static final String FILE_NAME = "eap_infinity_biginteger_cells";
|
||||
/**
|
||||
* 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充)
|
||||
*/
|
||||
public static InfinityStorageManager INSTANCE = null;
|
||||
/**
|
||||
* UUID -> 数据 的内存映射
|
||||
*/
|
||||
private final Map<UUID, InfinityDataStorage> cells = new HashMap<>();
|
||||
|
||||
public InfinityStorageManager() {
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 NBT 构造:用于在世界加载时从存档恢复数据
|
||||
*/
|
||||
public InfinityStorageManager(CompoundTag nbt) {
|
||||
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
|
||||
for (int i = 0; i < cellList.size(); i++) {
|
||||
CompoundTag cell = cellList.getCompound(i);
|
||||
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data")));
|
||||
}
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
|
||||
*/
|
||||
public static InfinityStorageManager getForLevel(ServerLevel level) {
|
||||
if (INSTANCE == null && level != null) {
|
||||
INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
|
||||
// 将内存中的所有 cell 序列化为一个 ListTag
|
||||
ListTag cellList = new ListTag();
|
||||
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
|
||||
CompoundTag cell = new CompoundTag();
|
||||
cell.putUUID("uuid", entry.getKey());
|
||||
cell.put("data", entry.getValue().serializeNBT());
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,9 @@
|
|||
"item.extendedae_plus.entity_speed_card.x4": "Entity Acceleration Card (x4)",
|
||||
"item.extendedae_plus.entity_speed_card.x8": "Entity Acceleration Card (x8)",
|
||||
"item.extendedae_plus.entity_speed_card.x16": "Entity Acceleration Card (x16)",
|
||||
"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",
|
||||
"tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s",
|
||||
"tooltip.extendedae_plus.entity_speed_card.max": "Max effective: %s x",
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
|
||||
"item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
|
||||
"item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
|
||||
"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寰",
|
||||
"tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
|
||||
"tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "extendedae_plus:item/infinity_biginteger_cell"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"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": {
|
||||
"item": "extendedae_plus:infinity_biginteger_cell",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
"PickFromWirelessMixin",
|
||||
"accessor.AbstractContainerScreenAccessor",
|
||||
"accessor.ScreenAccessor",
|
||||
"advancedae.client.gui.AdvPatternProviderScreenMixin",
|
||||
"advancedae.client.gui.SmallAdvPatternProviderScreenMixin",
|
||||
"ae2.QuartzCuttingKnifeItemMixin",
|
||||
"ae2.accessor.AEBaseScreenAccessor",
|
||||
"ae2.accessor.AEBaseScreenInvoker",
|
||||
|
|
@ -30,6 +32,13 @@
|
|||
"jei.accessor.BookmarkOverlayAccessor"
|
||||
],
|
||||
"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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user