diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 4253b20..1b551c5 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -9,11 +9,9 @@ import appeng.items.parts.PartModelsHelper; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; import com.extendedae_plus.config.ModConfigs; import com.extendedae_plus.init.*; -import com.extendedae_plus.util.storage.InfinityStorageManager; import com.mojang.logging.LogUtils; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.block.Blocks; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; @@ -22,10 +20,7 @@ import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfig; import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStartingEvent; -import net.neoforged.neoforge.event.server.ServerStoppedEvent; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; // The value here should match an entry in the META-INF/neoforge.mods.toml file @@ -59,8 +54,6 @@ public class ExtendedAEPlus { // Note that this is necessary if and only if we want *this* class (ExtendedAEPlus) to respond directly to events. // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. NeoForge.EVENT_BUS.register(this); - NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onServerStarted); - NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onServerStopped); // 注册配置:接入自定义的 ModConfigs modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC, "extendedae_plus-common.toml"); modContainer.registerConfig(ModConfig.Type.CLIENT, ModConfigs.CLIENT_SPEC, "extendedae_plus-client.toml"); @@ -134,29 +127,6 @@ public class ExtendedAEPlus { }); } - @Nullable - private static InfinityStorageManager storageManager; - - @Nullable - private static MinecraftServer storageManagerServer; - - private static void onServerStarted(ServerStartedEvent event) { - storageManagerServer = event.getServer(); - storageManager = InfinityStorageManager.getInstance(event.getServer()); - } - - private static void onServerStopped(ServerStoppedEvent event) { - if (storageManagerServer == event.getServer()) { - storageManagerServer = null; - storageManager = null; - } - } - - @Nullable - public static InfinityStorageManager currentStorageManager() { - return storageManager; - } - // You can use SubscribeEvent and let the Event Bus discover methods to call @SubscribeEvent public void onServerStarting(ServerStartingEvent event) { diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java index 3b7aa46..6d75f0f 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java @@ -2,9 +2,11 @@ package com.extendedae_plus.ae.api.storage; import appeng.api.storage.cells.ICellHandler; import appeng.api.storage.cells.ISaveProvider; -import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; +import com.extendedae_plus.util.storage.InfinityStorageManager; import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.Nullable; public class InfinityBigIntegerCellHandler implements ICellHandler { @@ -15,8 +17,12 @@ public class InfinityBigIntegerCellHandler implements ICellHandler { return is.getItem() instanceof InfinityBigIntegerCellItem; } + /** AE的这个方法是Nullable的,这里直接把NULL拦在外面即可 */ @Override - public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) { - return InfinityBigIntegerCellInventory.createInventory(is, container, ExtendedAEPlus.currentStorageManager()); + public @Nullable InfinityBigIntegerCellInventory getCellInventory(ItemStack is, @Nullable ISaveProvider container) { + if(ServerLifecycleHooks.getCurrentServer() == null) return null; + InfinityStorageManager manager = InfinityStorageManager.getInstance(); + if(manager == null || is == null) return null; + return new InfinityBigIntegerCellInventory(is, container, manager); } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index f050184..8c37a97 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -2,70 +2,91 @@ 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 appeng.core.AELog; -import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import com.extendedae_plus.util.storage.InfinityConstants; -import com.extendedae_plus.util.storage.InfinityDataStorage; import com.extendedae_plus.util.storage.InfinityStorageManager; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.core.component.DataComponents; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomData; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.util.Objects; import java.util.UUID; /** - * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

- * Original copyright (c) Technici4n

+ * MIT License + * + * Copyright (c) 2025 Frostbite-time + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ + +/** + * 用于无限元件的存储,这个类永远只会在服务端被构造 + * @author Frostbite */ public class InfinityBigIntegerCellInventory implements StorageCell { - private final InfinityBigIntegerCellItem cell; // 磁盘本身 + @NotNull private final ItemStack self; - @Nullable + @NotNull private final InfinityStorageManager storageManager; // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 + @Nullable private final ISaveProvider container; // 存储物品键和数量的映射 - private Object2ObjectMap AEKey2AmountsMap; - // 存储的物品种类数量 - private int totalAEKeyType; + @NotNull + private final Object2ObjectMap AEKey2AmountsMap; // 存储的物品总数 + @NotNull private BigInteger totalAEKey2Amounts = BigInteger.ZERO; - // 标记是否已持久化到 SavedData - private boolean isPersisted = true; + // 标记表示是否通知了需要保存 + private boolean isPersisted = false; - public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell, - ItemStack stack, - ISaveProvider saveProvider, - @Nullable InfinityStorageManager storageManager) { - // 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性 - this.cell = cell; + public InfinityBigIntegerCellInventory(@NotNull ItemStack stack, + @Nullable ISaveProvider saveProvider, + @NotNull InfinityStorageManager storageManager) { // 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据 this.self = stack; // 保存提供者,用于触发数据保存 this.container = saveProvider; - // 初始化 storedAmounts 为 null,延迟加载物品数据 - this.AEKey2AmountsMap = null; this.storageManager = storageManager; - // 初始化磁盘数据 - initData(); + // 走到这一步之后,我们不再管stack到底是什么物品,只要它能突破InfinityBigIntegerCellHandler的限制塞进来,那我们都接收 + // 这里立刻构建map,确保后续操作简洁,这里如果存在则取引用,如果不存在则建立空map,均不会造成性能问题 + this.AEKey2AmountsMap = storageManager.getOrCreateCell(getOrCreateUUID(stack, storageManager)).getAEKey2Amounts(); + + // 第一次构建我们全量遍历数量,后续增量更新 + for(BigInteger amount : AEKey2AmountsMap.values()) + { + totalAEKey2Amounts = totalAEKey2Amounts.add(amount); + } } // 将 BigInteger 格式化为带单位的字符串,保留两位小数 @@ -86,49 +107,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return df.format(bd.doubleValue()) + units[idx]; } - // 静态方法,创建存储单元库存 - public static InfinityBigIntegerCellInventory createInventory(ItemStack stack, - ISaveProvider saveProvider, - @Nullable InfinityStorageManager storageManager) { - // 检查物品堆栈是否为空 - Objects.requireNonNull(stack, "Cannot create cell inventory for null itemstack"); - // 检查物品是否为 IDISKCellItem 类型 - if (!(stack.getItem() instanceof InfinityBigIntegerCellItem cell)) { - return null; - } - // 创建并返回新的 DISKCellInventory 实例 - return new InfinityBigIntegerCellInventory(cell, stack, saveProvider, storageManager); - } - - // 获取磁盘的 InfinityDataStorage 数据 - private InfinityDataStorage getCellStorage() { - // 如果磁盘有 UUID,返回对应的 InfinityDataStorage - if (getUUID() != null && this.storageManager != null) { - return storageManager.getOrCreateCell(getUUID()); - } else { - // 否则返回空的 InfinityDataStorage - return InfinityDataStorage.EMPTY; - } - } - - // 初始化磁盘数据 - private void initData() { - // 如果磁盘有 UUID,加载存储的物品数据 - if (hasUUID()) { - this.totalAEKeyType = getCellStorage().amounts.size(); - this.totalAEKey2Amounts = getCellStorage().itemCount.equals(BigInteger.ZERO) ? - BigInteger.ZERO : - getCellStorage().itemCount; - - } else { - // 否则初始化为空 - this.totalAEKeyType = 0; - this.totalAEKey2Amounts = BigInteger.ZERO; - // 加载物品数据 - getCellStoredMap(); - } - } - // 获取存储单元的状态(空、部分填充) @Override public CellState getStatus() { @@ -149,107 +127,51 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 持久化存储单元数据到全局存储 @Override public void persist() { - if (this.isPersisted || this.storageManager == null) + if (this.isPersisted) return; + // 更新tooltip + if (this.totalAEKey2Amounts.equals(BigInteger.ZERO)) { - if (hasUUID()) { - this.storageManager.removeCell(getUUID()); - // 从 DataComponents.CUSTOM_DATA 里移除对应字段 - CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); - CompoundTag tag = data.copyTag(); - tag.remove(InfinityConstants.INFINITY_CELL_UUID); - tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL); - tag.remove(InfinityConstants.INFINITY_ITEM_TYPES); - // backward compat - tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT); + // 移除磁盘这一步有极少量优化效果,但是一个空map在内存中只占用极少的空间,持久化到磁盘中也只有极低的占用 + // 且SavedData并不限制存储的nbt的长度大小,因此,我们不再处理磁盘移除,简化数据管理逻辑 + CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + CompoundTag tag = data.copyTag(); + tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL); + tag.remove(InfinityConstants.INFINITY_ITEM_TYPES); + // backward compat + tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT); - self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); - - initData(); - } + self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); return; } - // 创建物品键列表 - ListTag keys = new ListTag(); - // 创建物品数量列表 - ListTag amounts = new ListTag(); - // 初始化物品总数 - BigInteger itemCount = BigInteger.ZERO; - - for (var entry : this.AEKey2AmountsMap.object2ObjectEntrySet()) { - BigInteger amount = entry.getValue(); - // 如果数量大于 0,添加到键和数量列表 - if (amount.compareTo(BigInteger.ZERO) > 0) { - keys.add(entry.getKey().toTagGeneric(this.storageManager.getRegistries())); - CompoundTag amountTag = new CompoundTag(); - amountTag.putByteArray("value", amount.toByteArray()); - amounts.add(amountTag); - - itemCount = itemCount.add(amount); - } - } - - if (keys.isEmpty()) { - this.storageManager.updateCell(getUUID(), new InfinityDataStorage()); - } else { - this.storageManager.modifyDisk(getUUID(), keys, amounts, itemCount); - } - - // 更新存储的物品种类数量 - this.totalAEKeyType = this.AEKey2AmountsMap.size(); - // 更新存储的物品总数 - this.totalAEKey2Amounts = itemCount; - // 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本 - - // 写回 DataComponents.CUSTOM_DATA(替代 getOrCreateTag) CompoundTag tag = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag(); - tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray()); - tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType); - tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); + tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, this.totalAEKey2Amounts.toByteArray()); + tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.AEKey2AmountsMap.size()); + tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, this.totalAEKey2Amounts.toByteArray()); self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); - // 标记数据已持久化 this.isPersisted = true; } - // 获取存储单元的描述(此处返回null,可自定义) + // 获取存储单元的描述 @Override public Component getDescription() { - return null; + return Component.empty(); } // 获取存储的物品总数 - public BigInteger getTotalAEKey2Amounts() { + public @NotNull BigInteger getTotalAEKey2Amounts() { return this.totalAEKey2Amounts; } // 获取存储的物品种类数量 public int getTotalAEKeyType() { - return this.totalAEKeyType; - } - - // 判断物品堆栈是否有UUID - public boolean hasUUID() { - CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); - return !data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID); - } - - // 获取物品堆栈的UUID - public UUID getUUID() { - CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); - if (!data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID)) { - return data.copyTag().getUUID(InfinityConstants.INFINITY_CELL_UUID); - } - return null; + return this.AEKey2AmountsMap.size(); } // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (AEKey2AmountsMap == null) { - AEKey2AmountsMap = new Object2ObjectOpenHashMap<>(); - this.loadCellStoredMap(); - } return AEKey2AmountsMap; } @@ -257,15 +179,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell { @Override public void getAvailableStacks(KeyCounter out) { BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); - if (this.getCellStoredMap() == null) return; for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) { AEKey key = entry.getKey(); BigInteger value = entry.getValue(); - - // 获取 KeyCounter 中已有的值 - long existing = out.get(key); + if(value.equals(BigInteger.ZERO)) continue; // 计算总和并限制到 Long.MAX_VALUE + long existing = out.get(key); BigInteger sum = BigInteger.valueOf(existing).add(value); long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue(); // 更新 KeyCounter @@ -279,60 +199,16 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } - - // 从存储中加载物品映射 - private void loadCellStoredMap() { - if (this.storageManager == null) { - return; - } - - boolean dataCorruption = false; - if (self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).isEmpty()) return; - - var keys = getCellStorage().keys; - var amounts = getCellStorage().amounts; - // 数据损坏 - if (keys.size() != amounts.size()) { - AELog.warn("Loading storage cell with mismatched amounts/tags: %d != %d", amounts.size(), keys.size()); - } - - var registries = this.storageManager.getRegistries(); - - // 遍历数量和键,加载到 AEKey2AmountsMap - for (int i = 0; i < amounts.size(); i++) { - AEKey key = AEKey.fromTagGeneric(registries,keys.getCompound(i)); - BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value")); - // 检查数据是否损坏 - if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) { - dataCorruption = true; - } else { - AEKey2AmountsMap.put(key, amount); - } - } - if (dataCorruption) { - this.saveChanges(); - } - } - // 标记数据需要保存,并通知容器或直接持久化 private void saveChanges() { - // 更新存储的物品种类数量 - this.totalAEKeyType = this.AEKey2AmountsMap.size(); - // 重置物品总数 - this.totalAEKey2Amounts = BigInteger.ZERO; - // 计算物品总数 - for (BigInteger AEKey2Amounts : this.AEKey2AmountsMap.values()) { - this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(AEKey2Amounts); - } - // 标记数据未持久化 + // 这里只需要这么多,container的统一持久化最终也会走persist this.isPersisted = false; - // 如果有保存提供者,通知保存 if (this.container != null) { this.container.saveChanges(); } else { - // 否则立即持久化 this.persist(); } + storageManager.setDirty(); // 数据设脏,这里只是通知,不会导致立刻保存,后续磁盘会随着保存事件一起保存 } // 插入物品到存储单元 @@ -342,40 +218,25 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (amount == 0) { return 0; } - // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && - itemKey.getItem() instanceof InfinityBigIntegerCellItem && - itemKey.get(DataComponents.CUSTOM_DATA) != null - ) { - return 0; - } - // 如果没有 UUID,且服务器端存储管理器已就绪,则生成 UUID 并初始化存储 - if (storageManager != null && !this.hasUUID()) { - // 取出自定义 NBT(如果没有就返回空) - CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); - CompoundTag tag = data.copyTag(); + // 这里存储自身不会造成实际上的问题,因为无限磁盘本身存储的nbt极为有限 + // 如果后续需要判断那些不允许被存储的磁盘,自行调用StroageCells中获取磁盘仓库的方法来判空 + canFitInsideCell + // 这里不做过多处理 + // if (what instanceof AEItemKey itemKey && + // itemKey.getItem() instanceof InfinityBigIntegerCellItem && + // itemKey.get(DataComponents.CUSTOM_DATA) != null + // ) { + // return 0; + // } - // 生成新的 UUID 并写入 - UUID newUUID = UUID.randomUUID(); - tag.putUUID(InfinityConstants.INFINITY_CELL_UUID, newUUID); - - // 回写到 ItemStack - self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); - - // 初始化存储 - this.storageManager.getOrCreateCell(newUUID); - - // 加载已存储的映射 - loadCellStoredMap(); - } // 获取当前物品数量 BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO); if (mode == Actionable.MODULATE) { // 实际插入,更新数量并保存 - BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); - getCellStoredMap().put(what, newAmount); + BigInteger toAdd = BigInteger.valueOf(amount); + this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(toAdd); + getCellStoredMap().put(what, currentAmount.add(toAdd)); this.saveChanges(); } return amount; @@ -393,6 +254,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 如果提取数量大于等于当前数量 if (requested.compareTo(currentAmount) >= 0) { if (mode == Actionable.MODULATE) { + this.totalAEKey2Amounts = this.totalAEKey2Amounts.subtract(currentAmount); getCellStoredMap().remove(what); this.saveChanges(); } @@ -400,6 +262,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } else { // 提取部分数量 if (mode == Actionable.MODULATE) { + this.totalAEKey2Amounts = this.totalAEKey2Amounts.subtract(requested); getCellStoredMap().put(what, currentAmount.subtract(requested)); this.saveChanges(); } @@ -414,4 +277,22 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 使用缓存的 totalStored,避免每次全表扫描 return formatBigInteger(totalAEKey2Amounts); } + + // 能用来构建这个inv的物品必须要有一个UUID + public static UUID getOrCreateUUID(ItemStack stack, InfinityStorageManager storageManager) + { + CustomData data = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + if(!data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID)) { + return data.copyTag().getUUID(InfinityConstants.INFINITY_CELL_UUID); + } else { + UUID uuid; + do { + uuid = UUID.randomUUID(); + } while (storageManager.hasUUID(uuid)); // 防御一下 + CompoundTag tag = data.copyTag(); + tag.putUUID(InfinityConstants.INFINITY_CELL_UUID, uuid); + stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); + return uuid; + } + } } diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java index 2ae9fb0..3e18c9f 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -79,7 +79,7 @@ public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchIt @Override public FuzzyMode getFuzzyMode(ItemStack itemStack) { - return null; + return FuzzyMode.IGNORE_ALL; // 这里返回什么都无所谓,cell inv最终被我们自行操作,但是出于安全考虑,还是不返回null } @Override diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java index 6462128..f1ecea8 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -1,48 +1,111 @@ package com.extendedae_plus.util.storage; +import appeng.api.stacks.AEKey; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import org.jetbrains.annotations.NotNull; import java.math.BigInteger; /** - * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

- * Original copyright (c) Technici4n

+ * MIT License + * + * Copyright (c) 2025 Frostbite-time + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ + +/** + * 无尽元件的数据实际读写 + * @author Frostbite */ public class InfinityDataStorage { - // 定义一个静态常量 EMPTY,表示一个空的 DataStorage 实例,用于默认或占位场景 - public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + // EMPTY也不需要,因为ae的硬盘获取实际上是允许返回null的,详见InfinityBigIntegerCellHandler的修改 - public ListTag keys; - public ListTag amounts; - // 存储磁盘中物品的总数,使用 BigInteger 支持大容量 - public BigInteger itemCount; + /** 同一个磁盘,在内存中总是只维护一份map,也就是这个。其他人永远只使用其引用 */ + private final Object2ObjectMap AEKey2Amounts; public InfinityDataStorage() { - this(new ListTag(), new ListTag(), BigInteger.ZERO); + this(new Object2ObjectOpenHashMap<>()); } - private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) { - this.keys = keys; - this.amounts = amounts; - this.itemCount = itemCount; + public InfinityDataStorage(Object2ObjectMap maps) { + AEKey2Amounts = maps; + } + + public Object2ObjectMap getAEKey2Amounts() + { + return AEKey2Amounts; } // 将 DataStorage 数据序列化为 NBT 格式 - public CompoundTag serializeNBT() { - CompoundTag nbt = new CompoundTag(); - nbt.put(InfinityConstants.INFINITY_CELL_KEYS, keys); - nbt.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts); - nbt.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); - return nbt; + public CompoundTag serializeNBT(@NotNull HolderLookup.Provider registry) { + // 创建物品键列表 + ListTag keys = new ListTag(); + // 创建物品数量列表 + ListTag amounts = new ListTag(); + + for (var entry : this.AEKey2Amounts.object2ObjectEntrySet()) { + BigInteger amount = entry.getValue(); + // 如果数量大于 0,添加到键和数量列表 + if (amount.compareTo(BigInteger.ZERO) > 0) { + try { + keys.add(entry.getKey().toTagGeneric(registry)); + CompoundTag amountTag = new CompoundTag(); + amountTag.putByteArray("value", amount.toByteArray()); + amounts.add(amountTag); + } + catch (Exception e) { + // 静默所有错误 + } + } + } + + CompoundTag KVTags = new CompoundTag(); + KVTags.put(InfinityConstants.INFINITY_CELL_KEYS, keys); + KVTags.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts); + return KVTags; } // 从 NBT 数据反序列化创建 DataStorage 实例 - public static InfinityDataStorage loadFromNBT(CompoundTag nbt) { + public static InfinityDataStorage loadFromNBT(CompoundTag nbt, @NotNull HolderLookup.Provider registry) { ListTag keys = nbt.getList(InfinityConstants.INFINITY_CELL_KEYS, ListTag.TAG_COMPOUND); ListTag amounts = nbt.getList(InfinityConstants.INFINITY_CELL_AMOUNTS, ListTag.TAG_COMPOUND); - BigInteger itemCount = new BigInteger(nbt.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT)); - // 使用加载的数据创建新的 DataStorage 实例 - return new InfinityDataStorage(keys, amounts, itemCount); + + Object2ObjectMap maps = new Object2ObjectOpenHashMap<>(); + maps.defaultReturnValue(BigInteger.ZERO); + for (int i = 0; i < keys.size(); i++) { + try { + AEKey key = AEKey.fromTagGeneric(registry,keys.getCompound(i)); + BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value")); + if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) continue; + maps.put(AEKey.fromTagGeneric(registry, keys.getCompound(i)), new BigInteger(amounts.getCompound(i).getByteArray("value"))); + } + catch (Exception e) { + // 忽略错误,保证读取时不崩溃 + // 这里我静默处理所有错误,后续你可以自己做其他处理 + } + } + + return new InfinityDataStorage(maps); } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index fabcfff..4708c1d 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -4,39 +4,55 @@ import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; +import net.neoforged.neoforge.server.ServerLifecycleHooks; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; -import java.lang.ref.WeakReference; -import java.math.BigInteger; import java.util.*; /** - * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

- * Original copyright (c) Technici4n

+ * MIT License + * + * Copyright (c) 2025 Frostbite-time + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * */ + +/** + * 无尽元件的SavedData管理类 + * @author Frostbite */ public class InfinityStorageManager extends SavedData { private static final Factory FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt); // 存储所有磁盘的Map,键为UUID,值为DataStorage对象 private final Map cells; - @Nullable - private WeakReference registries; - // 构造方法,初始化磁盘Map public InfinityStorageManager() { cells = new HashMap<>(); - // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 - this.setDirty(); } // 私有构造方法,用于从已有Map创建StorageManager private InfinityStorageManager(Map cells) { // 确保使用已加载的数据 this.cells = cells; - // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 - this.setDirty(); } @@ -55,7 +71,7 @@ public class InfinityStorageManager extends SavedData { // 获取当前索引的 CompoundTag,表示单个磁盘的数据 CompoundTag cell = cellList.getCompound(i); // 从 CompoundTag 中读取 UUID 和 DataStorage 数据,并存入 cells 映射 - cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA))); + cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA), registries)); } // 使用加载的 cells 数据创建新的 StorageManager 实例 return new InfinityStorageManager(cells); @@ -63,13 +79,13 @@ public class InfinityStorageManager extends SavedData { @Override - public CompoundTag save(CompoundTag nbt, HolderLookup.Provider provider) { + public @NotNull CompoundTag save(@NotNull CompoundTag nbt, HolderLookup.@NotNull Provider provider) { // 将内存中的所有 cell 序列化为一个 ListTag ListTag cellList = new ListTag(); for (Map.Entry entry : cells.entrySet()) { CompoundTag cell = new CompoundTag(); cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey()); - cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT()); + cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT(provider)); cellList.add(cell); } nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList); @@ -83,13 +99,6 @@ public class InfinityStorageManager extends SavedData { return Collections.unmodifiableSet(cells.keySet()); } - // 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) - public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { - cells.put(uuid, infinityDataStorage); - // 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘 - setDirty(); - } - // 删除某个 UUID 的持久化记录并标记为脏 public void removeCell(UUID uuid) { cells.remove(uuid); @@ -107,45 +116,18 @@ public class InfinityStorageManager extends SavedData { public InfinityDataStorage getOrCreateCell(UUID uuid) { // 检查 cells 映射中是否不存在指定 UUID if (!cells.containsKey(uuid)) { - updateCell(uuid, new InfinityDataStorage()); + InfinityDataStorage cell = new InfinityDataStorage(); + cells.put(uuid, cell); + return cell; } // 返回指定 UUID 对应的 DataStorage 对象 return cells.get(uuid); } - // 修改指定 UUID 的磁盘数据,包括堆栈键、数量和总项目数 - public void modifyDisk(UUID uuid, ListTag keys, ListTag amounts, BigInteger itemCount) { - // 获取或创建指定 UUID 的 DataStorage 对象 - InfinityDataStorage cellToModify = getOrCreateCell(uuid); - if (keys != null && amounts != null) { - cellToModify.keys = keys; - cellToModify.amounts = amounts; - } - // 更新 DataStorage 的 itemCount 字段 - cellToModify.itemCount = itemCount; - // 将修改后的 DataStorage 对象更新到 cells 映射 - updateCell(uuid, cellToModify); - } - - - public static InfinityStorageManager getInstance(MinecraftServer server) { - ServerLevel world = server.getLevel(ServerLevel.OVERWORLD); - var manager = world.getDataStorage().computeIfAbsent(FACTORY, InfinityConstants.SAVE_FILE_NAME); - manager.registries = new WeakReference<>(server.registryAccess()); - return manager; - } - - public HolderLookup.Provider getRegistries() { - var r = this.registries; - if (r == null) { - throw new IllegalStateException("StorageManager was not initialized properly."); - } - - var registries = r.get(); - if (registries == null) { - throw new IllegalStateException("Using a StorageManager whose server was already closed"); - } - - return registries; + public static @Nullable InfinityStorageManager getInstance() { + // 用neo提供的钩子拿服务端,再从服务端拿信息,注册表信息也无需在这里拿取,getCurrentServer始终会返回对当前来说正确的服务端 + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server == null) return null; + return server.overworld().getDataStorage().computeIfAbsent(FACTORY, InfinityConstants.SAVE_FILE_NAME); } }