Merge remote-tracking branch 'origin/develop/fix'

This commit is contained in:
GaLicn 2025-09-19 21:06:34 +08:00
commit 57706e9b65
25 changed files with 345 additions and 233 deletions

View File

@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
loom.platform = forge
# Mod properties
mod_version = 1.4.2
mod_version = 1.4.2-fix
maven_group = com.extendedae_plus
archives_name = extendedae_plus

View File

@ -3,7 +3,6 @@ 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.*;
@ -55,10 +54,6 @@ public class ExtendedAEPlus {
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);
}
/**
@ -128,7 +123,8 @@ public class ExtendedAEPlus {
// 在世界加载时注册/加载 SavedData
private static void onLevelLoad(LevelEvent.Load event) {
if (event.getLevel() instanceof ServerLevel serverLevel) {
InfinityStorageManager.getForLevel(serverLevel);
// 初始化自定义的文件 I/O 存储管理器
InfinityStorageManager.INSTANCE.initFromWorld(serverLevel);
}
}
}

View File

@ -17,8 +17,6 @@ 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;
@ -27,7 +25,6 @@ import java.text.DecimalFormat;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
/**
* InfinityBigIntegerCellInventory
* <p>
@ -59,8 +56,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
// 标记是否已持久化到 SavedData
private boolean isPersisted = true;
// 缓存的总存储量避免每次调用进行全表扫描
private BigInteger totalStored = BigInteger.ZERO;
/**
* 私有构造器通过 createInventory 工厂方法调用
@ -70,59 +66,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
*/
private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) {
this.stack = stack;
container = saveProvider;
// 不在构造时创建 storedMap推迟到实际访问或首次写入时初始化
this.container = saveProvider;
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());
}
initData();
}
// BigInteger 格式化为带单位的字符串保留两位小数
@ -142,14 +88,24 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
return DF.format(bd.doubleValue()) + units[idx];
}
// 创建存储单元库存实例的静态方法
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
return new InfinityBigIntegerCellInventory(stack, saveProvider);
}
private void initData() {
if (!hasUUID()) {
getCellStoredMap();
}
}
// 获取当前存储单元的数据存储对象
private InfinityDataStorage getCellStorage() {
if (this.getUUID() == null) {
// 如果没有UUID返回空存储
return InfinityDataStorage.EMPTY;
} else {
// 否则获取或创建对应UUID的存储
return getStorageInstance().getOrCreateCell(getUUID());
return InfinityStorageManager.INSTANCE.getOrCreateCell(this.getUUID());
}
}
@ -181,10 +137,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 获取物品堆栈的UUID
public UUID getUUID() {
if (this.hasUUID())
if (this.hasUUID()) {
return stack.getOrCreateTag().getUUID("uuid");
else
} else {
return null;
}
}
// 获取或初始化存储映射
@ -199,11 +156,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 从存储中加载物品映射
private void loadCellStoredMap() {
boolean corruptedTag = false; // 标记数据是否损坏
if (!stack.hasTag()) return;
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++) {
for (int i = 0; i < amounts.size(); i++) {
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
CompoundTag amtTag = amounts.getCompound(i);
try {
@ -221,15 +180,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
corruptedTag = true;
} else {
// storedMap 已在 getCellStoredMap() 中初始化直接使用字段以避免额外方法开销
storedMap.put(key, amount);
// 更新缓存的总数
totalStored = totalStored.add(amount);
getCellStoredMap().put(key, amount);
}
} catch (NumberFormatException ex) {
corruptedTag = true;
}
}
// 如果有损坏保存修正后的数据
// 如果有损坏尝试保存修正后的数据若全局管理器尚未就绪则保守处理
if (corruptedTag) {
this.saveChanges();
}
@ -243,10 +200,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 当存在容器时优先让容器统一处理持久化
container.saveChanges();
} else {
// 如果没有容器入队等待服务器 tick 在主线程统一持久化避免频繁 I/O
if (!PENDING_PERSIST.contains(this)) {
PENDING_PERSIST.offer(this);
}
persist();
}
}
@ -287,20 +241,25 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 持久化存储单元数据到全局存储
@Override
public void persist() {
if (this.isPersisted)
if (this.isPersisted) {
return;
}
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
if (map.isEmpty()) {
// 如果存储为空移除UUID和全局存储中的数据
// 如果存储为空保守处理写回空的 persisted 数据但不要从 ItemStack 上移除 uuid
if (this.hasUUID()) {
getStorageInstance().removeCell(getUUID());
if (stack.getTag() != null) {
stack.getTag().remove("uuid");
// 移除缓存的 total 字段
stack.getTag().remove("total");
// 如果存储为空移除UUID和全局存储中的数据并清理缓存的 types/total
if (hasUUID()) {
InfinityStorageManager.INSTANCE.removeCell(getUUID());
if (stack.getTag() != null) {
stack.getTag().remove("uuid");
stack.getTag().remove("types");
stack.getTag().remove("total");
}
initData();
}
return;
}
return;
}
// 构建要保存的Key和数量列表混合表示long string
ListTag amountTags = new ListTag();
@ -318,23 +277,35 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
amountTags.add(amt);
}
}
// 如果没有Key更新为空存储否则保存数据
if (keys.isEmpty()) {
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage());
// 清理缓存
if (stack.getTag() != null) {
stack.getTag().remove("types");
stack.getTag().remove("total");
}
} 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());
InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags);
// 缓存类型数量与总量到 ItemStack NBT避免每次 tooltip 或展示时重新统计
try {
if (stack.getTag() == null) stack.setTag(new CompoundTag());
int typesCount = keys.size();
stack.getOrCreateTag().putInt("types", typesCount);
java.math.BigInteger total = java.math.BigInteger.ZERO;
for (java.util.Map.Entry<AEKey, java.math.BigInteger> e : map.object2ObjectEntrySet()) {
java.math.BigInteger v = e.getValue();
if (v.compareTo(java.math.BigInteger.ZERO) > 0) {
total = total.add(v);
}
}
if (total.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
stack.getOrCreateTag().putLong("total", total.longValue());
} else {
stack.getOrCreateTag().putString("total", total.toString());
}
} catch (Exception ignored) {
}
// 将当前已存储的不同物品种类数缓存到 NBT键名: "types"用于客户端 tooltip 显示
int typesCount = this.getCellStoredMap().size();
stack.getOrCreateTag().putInt("types", typesCount);
}
isPersisted = true;
}
@ -343,17 +314,19 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
@Override
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
// 数量为0或类型不匹配直接返回
if (amount == 0)
if (amount == 0) {
return 0;
}
// 不允许存储无限单元自身
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) {
return 0;
// 如果没有UUID生成UUID并初始化存储
}
// 如果没有UUID生成UUID并初始化存储延迟创建全局存储以避免在 manager 未就绪时 NPE
if (!this.hasUUID()) {
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
getStorageInstance().getOrCreateCell(getUUID());
InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID());
// 确保 storedMap 初始化并从持久层加载数据
this.getCellStoredMap();
loadCellStoredMap();
}
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
@ -361,8 +334,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 实际插入更新数量并保存
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
map.put(what, newAmount);
// 更新 cached total
totalStored = totalStored.add(BigInteger.valueOf(amount));
this.saveChanges();
}
return amount;
@ -383,11 +354,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
} else {
ret = currentAmount.longValue();
}
if (mode == Actionable.MODULATE) {
if (mode == appeng.api.config.Actionable.MODULATE) {
map.remove(what);
// 更新 cached total
// 如果 currentAmount 大于 Long.MAX_VALUEtotalStored 减去 currentAmount 会保留大整数
totalStored = totalStored.subtract(currentAmount);
this.saveChanges();
}
return ret;
@ -395,8 +363,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 提取部分
if (mode == Actionable.MODULATE) {
map.put(what, currentAmount.subtract(requested));
// 更新 cached total
totalStored = totalStored.subtract(requested);
this.saveChanges();
}
return amount;
@ -408,6 +374,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
// 获取存储单元内所有物品的总数量格式化字符串
public String getTotalStorage() {
// 使用缓存的 totalStored避免每次全表扫描
return formatBigInteger(totalStored);
BigInteger total = BigInteger.ZERO;
for (BigInteger value : getCellStoredMap().values()) {
if (value.compareTo(BigInteger.ZERO) > 0) {
total = total.add(value);
}
}
return formatBigInteger(total);
}
}

View File

@ -1,7 +1,6 @@
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;
@ -14,7 +13,6 @@ 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 {
@ -39,15 +37,14 @@ public class InfinityBigIntegerCellItem extends Item {
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避免触发持久化或加载逻辑
// 优先使用 ItemStack NBT 缓存信息显示 tooltip客户端不应触发世界 I/O
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
// types 表示缓存的种类数
if (tag.contains("types")) {
try {
int types = tag.getInt("types");
@ -55,27 +52,25 @@ public class InfinityBigIntegerCellItem extends Item {
Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
);
} catch (Exception ignored) {
// ignore malformed value
}
}
// 读取并显示已缓存的 total支持 long string使用格式化函数展示友好单位
// total 支持 long string 两种表现形式
if (tag.contains("total")) {
BigInteger total = BigInteger.ZERO;
Tag t = tag.get("total");
try {
java.math.BigInteger total;
Tag t = tag.get("total");
if (t instanceof LongTag) {
total = BigInteger.valueOf(tag.getLong("total"));
total = java.math.BigInteger.valueOf(tag.getLong("total"));
} else {
String s = tag.getString("total");
total = new BigInteger(s);
total = new java.math.BigInteger(s);
}
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add(
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
);
} catch (Exception ignored) {
// 解析失败保持为 0
}
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add(
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
);
}
}
}

View File

@ -2,6 +2,8 @@ package com.extendedae_plus.integration.jei;
import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.forge.ForgeTypes;
import mezz.jei.api.ingredients.IIngredientType;
import mezz.jei.api.ingredients.ITypedIngredient;
import mezz.jei.api.runtime.IBookmarkOverlay;
import mezz.jei.api.runtime.IIngredientListOverlay;
@ -10,8 +12,13 @@ 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.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.ModList;
import org.spongepowered.asm.mixin.Pseudo;
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;
@ -19,10 +26,12 @@ import java.util.Optional;
/**
* 线程安全地缓存并访问 JEI Runtime
*/
@Pseudo
public final class JeiRuntimeProxy {
private static volatile IJeiRuntime RUNTIME;
private JeiRuntimeProxy() {}
private JeiRuntimeProxy() {
}
static void setRuntime(IJeiRuntime runtime) {
RUNTIME = runtime;
@ -135,9 +144,6 @@ public final class JeiRuntimeProxy {
return Collections.emptyList();
}
/**
* 将物品添加到 JEI 书签
*/
public static void addBookmark(ItemStack stack) {
IJeiRuntime rt = RUNTIME;
if (rt == null) return;
@ -154,6 +160,95 @@ 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<ITypedIngredient<FluidStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<FluidStack> 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.jei.MekanismJEI";
Class<?> jeiCls = Class.forName(mekanismJeiClass);
Field typeField = getField(clsName, jeiCls);
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();
}
}
}
private static @Nullable Field getField(String clsName, Class<?> jeiCls) throws NoSuchFieldException {
Field typeField = null;
if ("mekanism.api.chemical.gas.GasStack".equals(clsName)) {
typeField = jeiCls.getField("TYPE_GAS");
} else if ("mekanism.api.chemical.infuse.InfusionStack".equals(clsName)) {
typeField = jeiCls.getField("TYPE_INFUSION");
} else if ("mekanism.api.chemical.pigment.PigmentStack".equals(clsName)) {
typeField = jeiCls.getField("TYPE_PIGMENT");
} else if ("mekanism.api.chemical.slurry.SlurryStack".equals(clsName)) {
typeField = jeiCls.getField("TYPE_SLURRY");
}
return typeField;
}
/**
* JEI 书签移除物品
*/

View File

@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(AdvPatternProviderMenu.class)
public interface AdvPatternProviderMenuAdvancedAccessor {
@Accessor("logic")
@Accessor(value = "logic", remap = false)
AdvPatternProviderLogic eap$logic();
}

View File

@ -9,6 +9,7 @@ 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.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -18,7 +19,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AdvPatternProviderMenu.class)
public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync {
@Shadow
@Final
@Shadow(remap = false)
protected AdvPatternProviderLogic logic;
// 选择一个未占用的 GUI 同步 idAE2 已用到 7这里使用 21 以避冲突
@ -38,7 +40,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv
}
// 构造器尾注入public ctor
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;
@ -49,7 +51,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv
}
// 构造器尾注入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"))
@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"), remap = false)
private void eap$initAdvancedSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;

View File

@ -9,6 +9,7 @@ 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.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -18,7 +19,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AdvPatternProviderMenu.class)
public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
@Shadow
@Final
@Shadow(remap = false)
protected AdvPatternProviderLogic logic;
@Unique
@ -35,7 +37,7 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv
}
}
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initSmartSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;
@ -45,7 +47,7 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv
} 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"))
@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"), remap = false)
private void eap$initSmartSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;

View File

@ -10,23 +10,23 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(MEStorageMenu.class)
public interface MEStorageMenuAccessor {
@Accessor("storage")
@Accessor(value = "storage", remap = false)
@Nullable
MEStorage getStorage();
@Accessor("powerSource")
@Accessor(value = "powerSource", remap = false)
@Nullable
IEnergySource getPowerSource();
@Accessor("hasPower")
@Accessor(value = "hasPower", remap = false)
boolean getHasPower();
// Access client-side config manager mirror used for syncing settings
@Accessor("clientCM")
@Accessor(value = "clientCM", remap = false)
IConfigManager getClientCM();
// Access server-side config manager
@Accessor("serverCM")
@Accessor(value = "serverCM", remap = false)
@Nullable
IConfigManager getServerCM();
}

View File

@ -7,9 +7,9 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(PatternEncodingTermMenu.class)
public interface PatternEncodingTermMenuAccessor {
@Accessor("encodedPatternSlot")
@Accessor(value = "encodedPatternSlot",remap = false)
RestrictedInputSlot eap$getEncodedPatternSlot();
@Accessor("blankPatternSlot")
@Accessor(value = "blankPatternSlot",remap = false)
RestrictedInputSlot eap$getBlankPatternSlot();
}

View File

@ -8,9 +8,9 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(PatternProviderLogic.class)
public interface PatternProviderLogicAccessor {
@Accessor("host")
@Accessor(value = "host", remap = false)
PatternProviderLogicHost eap$host();
@Accessor("mainNode")
@Accessor(value = "mainNode", remap = false)
IManagedGridNode eap$mainNode();
}

View File

@ -9,6 +9,6 @@ import java.util.Set;
@Mixin(PatternProviderLogic.class)
public interface PatternProviderLogicPatternInputsAccessor {
@Accessor("patternInputs")
@Accessor(value = "patternInputs",remap = false)
Set<AEKey> eap$patternInputs();
}

View File

@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(PatternProviderMenu.class)
public interface PatternProviderMenuAdvancedAccessor {
@Accessor("logic")
@Accessor(value = "logic", remap = false)
PatternProviderLogic eap$logic();
}

View File

@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(CraftingTreeNode.class)
public interface CraftingTreeNodeAccessor {
@Accessor("what")
@Accessor(value = "what", remap = false)
AEKey eap$getWhat();
}

View File

@ -33,7 +33,8 @@ public abstract class CraftingTreeProcessMixin {
@ModifyVariable(
method = "<init>(Lappeng/api/networking/crafting/ICraftingService;Lappeng/crafting/CraftingCalculation;Lappeng/api/crafting/IPatternDetails;Lappeng/crafting/CraftingTreeNode;)V",
at = @At("HEAD"),
argsOnly = true
argsOnly = true,
remap = false
)
private static IPatternDetails eap$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) {
try {

View File

@ -312,7 +312,7 @@ public abstract class AEBaseScreenMixin {
}
@Shadow
@Shadow(remap = false)
protected void setTextContent(String id, Component content) {};
@Inject(method = "updateBeforeRender", at = @At("RETURN"), remap = false)

View File

@ -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.minecraftforge.fml.ModList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -44,9 +47,27 @@ public class CraftConfirmMenuGoBackMixin {
// 仅在按住 shift 时为缺失的条目添加 JEI 书签
for (CraftingPlanSummaryEntry entry : entries) {
if (entry.getMissingAmount() > 0) {
JeiRuntimeProxy.addBookmark(entry.getWhat().wrapForDisplayOrFilter());
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) {}
} catch (Throwable ignored) {
}
}
}

View File

@ -14,6 +14,7 @@ import appeng.menu.slot.RestrictedInputSlot;
import com.extendedae_plus.mixin.ae2.accessor.MEStorageMenuAccessor;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -28,7 +29,8 @@ public abstract class PatternEncodingTermMenuMixin {
@Unique
private boolean eap$blankAutoFilled = false;
@Shadow
@Final
@Shadow(remap = false)
private RestrictedInputSlot blankPatternSlot;
@Unique
@ -87,7 +89,7 @@ public abstract class PatternEncodingTermMenuMixin {
}
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;Z)V",
at = @At("TAIL"))
at = @At("TAIL"), remap = false)
private void eap$autoFillBlankPattern(MenuType<?> menuType, int id, Inventory ip,
IPatternTerminalMenuHost host, boolean bindInventory,
CallbackInfo ci) {
@ -95,7 +97,7 @@ public abstract class PatternEncodingTermMenuMixin {
}
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;)V",
at = @At("TAIL"))
at = @At("TAIL"), remap = false)
private void eap$autoFillCtor3(int id, Inventory ip, IPatternTerminalMenuHost host, CallbackInfo ci) {
eap$tryFill(host, ip);
}

View File

@ -9,6 +9,7 @@ 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 org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -20,7 +21,8 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
@Mixin(PatternProviderMenu.class)
public abstract class PatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync {
@Shadow
@Final
@Shadow(remap = false)
protected PatternProviderLogic logic;
// 选择一个未占用的 GUI 同步 idAE2 已用到 7这里使用 20 以避冲突
@ -40,7 +42,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
}
// 构造器尾注入public ctor
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;
@ -51,7 +53,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
}
// 构造器尾注入protected ctor with MenuType
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initAdvancedSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;

View File

@ -9,6 +9,7 @@ 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 org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@ -20,7 +21,8 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
@Mixin(PatternProviderMenu.class)
public abstract class PatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
@Shadow
@Final
@Shadow(remap = false)
protected PatternProviderLogic logic;
@Unique
@ -37,7 +39,7 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
}
}
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initSmartSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;
@ -49,7 +51,7 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
}
}
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
private void eap$initSmartSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try {
var l = this.logic;

View File

@ -32,7 +32,7 @@ public abstract class ContainerWirelessExPatternTerminalMixin implements IAction
private Player epp$player;
// 明确目标构造签名<init>(int, Inventory, HostWirelessExPAT)
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/common/me/itemhost/HostWirelessExPAT;)V", at = @At("TAIL"), require = 0)
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/common/me/itemhost/HostWirelessExPAT;)V", at = @At("TAIL"), require = 0, remap = false)
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, HostWirelessExPAT host, CallbackInfo ci) {
this.epp$player = playerInventory.player;
// 注册上传动作参数顺序必须与客户端 CGenericPacket 保持一致

View File

@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BookmarkOverlay.class)
public interface BookmarkOverlayAccessor {
@Accessor("bookmarkList")
@Accessor(value = "bookmarkList", remap = false)
BookmarkList eap$getBookmarkList();
}

View File

@ -44,8 +44,8 @@ public class InfinityDataStorage {
*/
public CompoundTag serializeNBT() {
CompoundTag nbt = new CompoundTag();
nbt.put("keys", keys);
nbt.put("amounts", amounts);
nbt.put("keys", this.keys);
nbt.put("amounts", this.amounts);
return nbt;
}

View File

@ -2,100 +2,124 @@ package com.extendedae_plus.util.storage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.NotNull;
import net.minecraft.world.level.storage.LevelResource;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.AtomicMoveNotSupportedException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* InfinityStorageManager
* <p>
* 世界级别的持久化容器集中管理所有 InfinityBigInteger 存储单元的序列化数据
* 功能要点
* - 在世界加载时从存档恢复所有 cell 的数据
* - 提供按 UUID 获取/创建单个 cell 的数据容器
* - 在世界保存时将内存数据打包为 NBT 写回存档
*
* 替代之前基于 SavedData 的实现本类使用手动文件 I/O world 目录下保存 NBT 数据
* 以避免依赖 Minecraft SavedData 机制
* 数据保持与之前兼容的 NBT 结构 Compound 包含 "list" => ListTag of Compound { uuid, data }
*/
public class InfinityStorageManager extends SavedData {
public class InfinityStorageManager {
public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat";
/** 全局单例,由 mod 在 world load 时初始化 */
public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager();
/**
* 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<>();
private Path saveFilePath = null;
public InfinityStorageManager() {
setDirty();
}
/**
* NBT 构造用于在世界加载时从存档恢复数据
* 初始化并从 world 保存目录加载数据若文件不存在则保持空状态
*/
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")));
public void initFromWorld(@Nullable ServerLevel serverLevel) {
if (serverLevel == null) return;
try {
File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile();
// 保存到 world/<modid>/ 文件夹下避免与其它 mod 冲突
File modDir = new File(worldFolder, "data");
if (!modDir.exists()) modDir.mkdirs();
saveFilePath = new File(modDir, FILE_NAME).toPath();
if (Files.exists(saveFilePath)) {
CompoundTag root = NbtIo.readCompressed(saveFilePath.toFile());
ListTag cellList = root.getList("list", Tag.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")));
}
}
} catch (IOException e) {
// 读取失败保持空并打印栈追踪以便调试
e.printStackTrace();
}
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);
public synchronized void saveToFile() {
if (saveFilePath == null) return;
try {
CompoundTag root = new CompoundTag();
ListTag cellList = new ListTag();
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
// 跳过可能的 null key防止写入时 NPE
if (entry.getKey() == null || entry.getValue() == null) continue;
CompoundTag cell = new CompoundTag();
cell.putUUID("uuid", entry.getKey());
cell.put("data", entry.getValue().serializeNBT());
cellList.add(cell);
}
root.put("list", cellList);
// 使用压缩写入到临时文件然后原子替换目标文件以避免半成品/0字节文件
Path tmp = saveFilePath.resolveSibling(FILE_NAME + ".tmp");
File tmpFile = tmp.toFile();
// 确保临时文件的目录存在
if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) {
tmpFile.getParentFile().mkdirs();
}
NbtIo.writeCompressed(root, tmpFile);
try {
Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException ex) {
// 若底层文件系统不支持原子移动退回到非原子替换
Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
e.printStackTrace();
}
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) {
if (uuid == null) return; // 忽略无效 UUID
cells.put(uuid, infinityDataStorage);
setDirty();
saveToFile();
}
/**
* 获取或创建某个 UUID 对应的数据容器
*/
public InfinityDataStorage getOrCreateCell(UUID uuid) {
if (uuid == null) {
return InfinityDataStorage.EMPTY;
}
if (!cells.containsKey(uuid)) {
updateCell(uuid, new InfinityDataStorage());
InfinityDataStorage newCell = new InfinityDataStorage();
cells.put(uuid, newCell);
saveToFile();
}
return cells.get(uuid);
}
/**
* 修改某个 UUID 对应的键与数量列表并保存新的签名stackAmounts ListTag 字符串列表
*/
public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) {
if (cellID == null) return;
InfinityDataStorage cellToModify = getOrCreateCell(cellID);
if (stackKeys != null && stackAmounts != null) {
cellToModify.keys = stackKeys;
@ -104,11 +128,9 @@ public class InfinityStorageManager extends SavedData {
updateCell(cellID, cellToModify);
}
/**
* 删除某个 UUID 的持久化记录并标记为脏
*/
public void removeCell(UUID uuid) {
if (uuid == null) return;
cells.remove(uuid);
setDirty();
saveToFile();
}
}

View File

@ -16,8 +16,8 @@
"ae2.accessor.PatternAccessTermScreenAccessor",
"ae2.accessor.PatternAccessTermScreenSlotsRowAccessor",
"ae2.client.gui.AEBaseScreenMixin",
"ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.InterfaceScreenMixin",
"ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.PatternProviderCloseMixin",
"ae2.client.gui.PatternProviderScreenMixin",
"ae2.client.gui.SlotGridLayoutMixin",