From 92ed35a43e26469f9c6a17cf70ed1a4470319637 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Mon, 15 Sep 2025 18:06:49 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E7=B3=BB=E5=88=97=E5=AF=B9=E9=AB=98=E7=BA=A7ae?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=99=A8=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ernProviderLogicContainsRedirectMixin.java | 38 +++++ ...vPatternProviderLogicPatternsAccessor.java | 14 ++ ...dvPatternProviderMenuAdvancedAccessor.java | 12 ++ .../gui/AdvPatternProviderScreenMixin.java | 147 ++++++++++++++++++ .../SmallAdvPatternProviderScreenMixin.java | 147 ++++++++++++++++++ .../AdvPatternProviderLogicAdvancedMixin.java | 109 +++++++++++++ .../AdvPatternProviderLogicDoublingMixin.java | 91 +++++++++++ .../AdvPatternProviderMenuAdvancedMixin.java | 66 ++++++++ .../AdvPatternProviderMenuDoublingMixin.java | 62 ++++++++ .../gui/PatternProviderScreenMixin.java | 5 - .../PatternProviderLogicAdvancedMixin.java | 14 +- .../PatternProviderLogicDoublingMixin.java | 14 +- .../PatternProviderMenuAdvancedMixin.java | 15 +- .../PatternProviderMenuDoublingMixin.java | 1 - .../ToggleAdvancedBlockingC2SPacket.java | 39 +++-- .../network/ToggleSmartDoublingC2SPacket.java | 30 ++-- .../resources/extendedae_plus.mixins.json | 9 ++ 17 files changed, 758 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java new file mode 100644 index 0000000..0773388 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java @@ -0,0 +1,38 @@ +package com.extendedae_plus.mixin.advancedae; + +import appeng.api.crafting.IPatternDetails; +import com.extendedae_plus.content.ScaledProcessingPattern; +import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +/**适配 + * Redirect PatternProviderLogic.pushPattern 中对 List.contains 的调用, + * 在遇到缩放样板时回退匹配到原始样板实例。 + */ +@Mixin(value = AdvPatternProviderLogic.class, remap = false) +public class AdvPatternProviderLogicContainsRedirectMixin { + + @Redirect(method = "pushPattern", + at = @At( + value = "INVOKE", + target = "Ljava/util/List;contains(Ljava/lang/Object;)Z") + ) + private boolean eap$patternsContains(List list, Object o) { + try { + if (o instanceof ScaledProcessingPattern scaled) { + IPatternDetails base = scaled.getOriginal(); + if (base != null && list.indexOf(base) != -1) { + return true; + } + } + // 使用 indexOf 避免再次触发对 List.contains 的 redirect(防止递归) + return list.indexOf(o) != -1; + } catch (Throwable t) { + return list.indexOf(o) != -1; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java new file mode 100644 index 0000000..a75643c --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderLogicPatternsAccessor.java @@ -0,0 +1,14 @@ +package com.extendedae_plus.mixin.advancedae.accessor; + +import appeng.api.crafting.IPatternDetails; +import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(value = AdvPatternProviderLogic.class, remap = false) +public interface AdvPatternProviderLogicPatternsAccessor { + @Accessor("patterns") + List eap$patterns(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java new file mode 100644 index 0000000..bf6987e --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/accessor/AdvPatternProviderMenuAdvancedAccessor.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.mixin.advancedae.accessor; + +import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; +import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(AdvPatternProviderMenu.class) +public interface AdvPatternProviderMenuAdvancedAccessor { + @Accessor("logic") + AdvPatternProviderLogic eap$logic(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java new file mode 100644 index 0000000..5cd6c30 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/AdvPatternProviderScreenMixin.java @@ -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 { + + @Unique + private SettingToggleButton eap$AdvancedBlockingToggle; + + @Unique + private boolean eap$AdvancedBlockingEnabled = false; + + @Unique + private SettingToggleButton eap$SmartDoublingToggle; + + @Unique + private boolean eap$SmartDoublingEnabled = false; + + public AdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { + super((AdvPatternProviderMenu) menu, playerInventory, title, style); + } + + @Inject(method = "", at = @At("RETURN")) + private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 使用 @GuiSync 初始化 + try { + if (menu instanceof PatternProviderMenuAdvancedSync sync) { + this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); + } + } catch (Throwable t) { + LOGGER.error("Error initializing advanced sync", t); + } + + // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡” + this.eap$AdvancedBlockingToggle = new SettingToggleButton<>( + Settings.BLOCKING_MODE, + this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO, + (btn, backwards) -> { + // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传 + ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket()); + } + ) { + @Override + public java.util.List getTooltipMessage() { + boolean enabled = eap$AdvancedBlockingEnabled; + var title = Component.literal("智能阻挡"); + var line = enabled + ? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)") + : Component.literal("已禁用:这么好的功能为什么不打开呢"); + return java.util.List.of(title, line); + } + }; + // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致 + this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + + this.addToLeftToolbar(this.eap$AdvancedBlockingToggle); + + // 智能翻倍按钮:与高级阻挡同款样式,点击仅发送C2S,状态由@GuiSync驱动 + try { + if (menu instanceof PatternProviderMenuDoublingSync sync2) { + this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced(); + } + } catch (Throwable t) { + LOGGER.error("Error initializing smart doubling sync", t); + } + + this.eap$SmartDoublingToggle = new SettingToggleButton<>( + Settings.BLOCKING_MODE, + this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO, + (btn, backwards) -> { + ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket()); + } + ) { + @Override + public java.util.List getTooltipMessage() { + boolean enabled = eap$SmartDoublingEnabled; + var title = Component.literal("智能翻倍"); + var line = enabled + ? Component.literal("已启用:根据请求量对处理样板进行智能缩放") + : Component.literal("已禁用:按原始样板数量进行发配"); + return java.util.List.of(title, line); + } + }; + + this.eap$SmartDoublingToggle.set(this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$SmartDoublingToggle); + } + + // 每帧刷新:仅从菜单(@GuiSync)同步布尔值,保持按钮状态一致 + @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false) + private void eap$updateAdvancedBlocking(CallbackInfo ci) { + if (this.eap$AdvancedBlockingToggle != null) { + boolean desired = this.eap$AdvancedBlockingEnabled; + if (this.menu instanceof PatternProviderMenuAdvancedSync sync) { + desired = sync.eap$getAdvancedBlockingSynced(); + } + this.eap$AdvancedBlockingEnabled = desired; + this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO); + } + + if (this.eap$SmartDoublingToggle != null) { + boolean desired2 = this.eap$SmartDoublingEnabled; + if (this.menu instanceof PatternProviderMenuDoublingSync sync2) { + desired2 = sync2.eap$getSmartDoublingSynced(); + } + this.eap$SmartDoublingEnabled = desired2; + this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO); + } + + if ((Object) this instanceof GuiExPatternProvider) { + try { + ((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); + } catch (Throwable t) { + LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); + } + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java new file mode 100644 index 0000000..106207f --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/client/gui/SmallAdvPatternProviderScreenMixin.java @@ -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 { + + @Unique + private SettingToggleButton eap$AdvancedBlockingToggle; + + @Unique + private boolean eap$AdvancedBlockingEnabled = false; + + @Unique + private SettingToggleButton eap$SmartDoublingToggle; + + @Unique + private boolean eap$SmartDoublingEnabled = false; + + public SmallAdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { + super((SmallAdvPatternProviderMenu) menu, playerInventory, title, style); + } + + @Inject(method = "", at = @At("RETURN")) + private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 使用 @GuiSync 初始化 + try { + if (menu instanceof PatternProviderMenuAdvancedSync sync) { + this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); + } + } catch (Throwable t) { + LOGGER.error("Error initializing advanced sync", t); + } + + // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡” + this.eap$AdvancedBlockingToggle = new SettingToggleButton<>( + Settings.BLOCKING_MODE, + this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO, + (btn, backwards) -> { + // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传 + ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket()); + } + ) { + @Override + public java.util.List getTooltipMessage() { + boolean enabled = eap$AdvancedBlockingEnabled; + var title = Component.literal("智能阻挡"); + var line = enabled + ? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)") + : Component.literal("已禁用:这么好的功能为什么不打开呢"); + return java.util.List.of(title, line); + } + }; + // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致 + this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + + this.addToLeftToolbar(this.eap$AdvancedBlockingToggle); + + // 智能翻倍按钮:与高级阻挡同款样式,点击仅发送C2S,状态由@GuiSync驱动 + try { + if (menu instanceof PatternProviderMenuDoublingSync sync2) { + this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced(); + } + } catch (Throwable t) { + LOGGER.error("Error initializing smart doubling sync", t); + } + + this.eap$SmartDoublingToggle = new SettingToggleButton<>( + Settings.BLOCKING_MODE, + this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO, + (btn, backwards) -> { + ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket()); + } + ) { + @Override + public java.util.List getTooltipMessage() { + boolean enabled = eap$SmartDoublingEnabled; + var title = Component.literal("智能翻倍"); + var line = enabled + ? Component.literal("已启用:根据请求量对处理样板进行智能缩放") + : Component.literal("已禁用:按原始样板数量进行发配"); + return java.util.List.of(title, line); + } + }; + + this.eap$SmartDoublingToggle.set(this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$SmartDoublingToggle); + } + + // 每帧刷新:仅从菜单(@GuiSync)同步布尔值,保持按钮状态一致 + @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false) + private void eap$updateAdvancedBlocking(CallbackInfo ci) { + if (this.eap$AdvancedBlockingToggle != null) { + boolean desired = this.eap$AdvancedBlockingEnabled; + if (this.menu instanceof PatternProviderMenuAdvancedSync sync) { + desired = sync.eap$getAdvancedBlockingSynced(); + } + this.eap$AdvancedBlockingEnabled = desired; + this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO); + } + + if (this.eap$SmartDoublingToggle != null) { + boolean desired2 = this.eap$SmartDoublingEnabled; + if (this.menu instanceof PatternProviderMenuDoublingSync sync2) { + desired2 = sync2.eap$getSmartDoublingSynced(); + } + this.eap$SmartDoublingEnabled = desired2; + this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO); + } + + if ((Object) this instanceof GuiExPatternProvider) { + try { + ((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); + } catch (Throwable t) { + LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); + } + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java new file mode 100644 index 0000000..8df0927 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java @@ -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 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(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java new file mode 100644 index 0000000..85f7041 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java @@ -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(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java new file mode 100644 index 0000000..db4ae90 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java @@ -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 = "(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 = "(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; + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java new file mode 100644 index 0000000..7e63986 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java @@ -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 = "(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 = "(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; + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java index 2fb5edd..863fab3 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenMixin.java @@ -65,7 +65,6 @@ public abstract class PatternProviderScreenMixin 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 } }; // 初始化后立刻对齐当前@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 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 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 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); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java index 5cff7ca..18e5a4e 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java @@ -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(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java index 462506f..ff3152d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java @@ -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(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java index 5e8fade..8a1fb05 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuAdvancedMixin.java @@ -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) { - } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java index 0e184f4..822c22f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuDoublingMixin.java @@ -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); } } } diff --git a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java b/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java index f58f500..2711206 100644 --- a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java @@ -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); diff --git a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java b/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java index 3ad6405..88d832a 100644 --- a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java @@ -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); diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index af79b32..edc5469 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -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", @@ -29,6 +31,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", From 77c5246232ba1439ac7f277f0b4876400e3eff3f Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Mon, 15 Sep 2025 18:25:59 +0800 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20PatternProviderCloseMixin=E6=B7=B7?= =?UTF-8?q?=E5=85=A5=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixin/ae2/client/gui/PatternProviderCloseMixin.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderCloseMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderCloseMixin.java index 767e888..4613b2c 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderCloseMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderCloseMixin.java @@ -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 { From 9bdda0526f532d1ff7f2611ccd397b6c51315885 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 16 Sep 2025 17:53:17 +0800 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=E6=97=A0=E9=99=90=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E7=A3=81=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../com/extendedae_plus/ExtendedAEPlus.java | 15 + .../InfinityBigIntegerCellHandler.java | 36 ++ .../InfinityBigIntegerCellInventory.java | 375 ++++++++++++++++++ .../ae/items/InfinityBigIntegerCellItem.java | 68 ++++ .../extendedae_plus/init/ModCreativeTabs.java | 2 + .../com/extendedae_plus/init/ModItems.java | 6 + .../util/storage/InfinityDataStorage.java | 61 +++ .../util/storage/InfinityStorageManager.java | 114 ++++++ 9 files changed, 678 insertions(+) create mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java create mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java create mode 100644 src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java diff --git a/README.md b/README.md index d8d6011..ca05466 100644 --- a/README.md +++ b/README.md @@ -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 请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。 diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 5bc94d1..6a84267 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -1,14 +1,19 @@ 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.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,6 +51,7 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); + MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); // 注册通用配置 ModConfig.init(); // ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); @@ -55,6 +61,8 @@ public class ExtendedAEPlus { * 通用初始化设置 */ private void commonSetup(final FMLCommonSetupEvent event) { + StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE); + // 注册本模组网络通道与数据包 event.enqueueWork(() -> { // 注册升级卡 @@ -104,4 +112,11 @@ public class ExtendedAEPlus { } catch (Exception ignored) {} } } + + // 在世界加载时注册/加载 SavedData + private static void onLevelLoad(LevelEvent.Load event) { + if (event.getLevel() instanceof ServerLevel serverLevel) { + InfinityStorageManager.getForLevel(serverLevel); + } + } } diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java new file mode 100644 index 0000000..bc7c29c --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java @@ -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); + } +} \ 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 new file mode 100644 index 0000000..19273c4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -0,0 +1,375 @@ +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.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * InfinityBigIntegerCellInventory + *

+ * 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。 + * 主要职责: + * - 在内存中维护条目映射 (AEKey -> BigInteger 数量) + * - 提供插入/提取/列举/持久化等操作的实现 + * - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化 + *

+ * 重要字段: + * - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息 + * - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化 + * - storedMap: 延迟初始化的内存映射,减少未使用时内存占用 + * - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描 + * - isPersisted: 标记内存状态是否已同步到持久层 + */ +public class InfinityBigIntegerCellInventory implements StorageCell { + + // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) + private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); + // 数字格式化对象,保留两位小数(复用以减少对象分配) + private static final DecimalFormat DF = new DecimalFormat("#.##"); + + static { + // 在类加载时注册服务器 tick 监听器,用于在主线程合并写入 + try { + MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); + } catch (Throwable ignored) { + // 保守降级:若注册失败,不阻塞实例化 + } + } + + // 关联的 ItemStack(含可能的 uuid NBT) + private final ItemStack stack; + // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 + private final ISaveProvider container; + // 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化) + private Object2ObjectMap storedMap = null; + // 标记是否已持久化到 SavedData + private boolean isPersisted = true; + // 缓存的总存储量,避免每次调用进行全表扫描 + private BigInteger totalStored = BigInteger.ZERO; + + /** + * 私有构造器:通过 createInventory 工厂方法调用 + * + * @param stack 关联的物品堆 + * @param saveProvider AE2 的保存回调(可为 null) + */ + private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { + this.stack = stack; + container = saveProvider; + // 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化 + this.storedMap = null; + } + + // 创建存储单元库存实例的静态方法 + static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { + if (stack.getItem() instanceof InfinityBigIntegerCellItem) { + return new InfinityBigIntegerCellInventory(stack, saveProvider); + } + return null; + } + + // 获取全局存储实例 + private static InfinityStorageManager getStorageInstance() { + return InfinityStorageManager.INSTANCE; + } + + // 服务器 tick 回调:合并并执行待持久化项 + private 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) { + // 忽略单项错误,继续处理其余队列 + } + } + } + + // 将 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 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 map = getCellStoredMap(); + for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { + BigInteger v = entry.getValue(); + long toAdd = v.compareTo(maxLong) > 0 ? Long.MAX_VALUE : v.longValue(); + out.add(entry.getKey(), toAdd); + } + } + + // 持久化存储单元数据到全局存储 + @Override + public void persist() { + if (this.isPersisted) + return; + Object2ObjectMap 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 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()); + } + } + 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 map = this.getCellStoredMap(); + BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + if (mode == Actionable.MODULATE) { + // 实际插入,更新数量并保存 + BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); + map.put(what, newAmount); + // 更新 cached total + totalStored = totalStored.add(BigInteger.valueOf(amount)); + this.saveChanges(); + } + return amount; + } + + // 从存储单元提取物品 + @Override + public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { + Object2ObjectMap map = this.getCellStoredMap(); + BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + if (currentAmount.compareTo(BigInteger.ZERO) > 0) { + BigInteger requested = BigInteger.valueOf(amount); + if (currentAmount.compareTo(requested) <= 0) { + // 提取全部 + long ret; + if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + ret = Long.MAX_VALUE; + } else { + ret = currentAmount.longValue(); + } + if (mode == Actionable.MODULATE) { + map.remove(what); + // 更新 cached total + // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 + totalStored = totalStored.subtract(currentAmount); + this.saveChanges(); + } + return ret; + } else { + // 提取部分 + if (mode == Actionable.MODULATE) { + map.put(what, currentAmount.subtract(requested)); + // 更新 cached total + totalStored = totalStored.subtract(requested); + this.saveChanges(); + } + return amount; + } + } + return 0; + } + + // 获取存储单元内所有物品的总数量(格式化字符串) + public String getTotalStorage() { + // 使用缓存的 totalStored,避免每次全表扫描 + return formatBigInteger(totalStored); + } +} diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java new file mode 100644 index 0000000..5b550a1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -0,0 +1,68 @@ +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 tooltip, + @NotNull TooltipFlag context) { + 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)) + ); + // 读取并显示已缓存的 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)) + ); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index 68bdba4..dc4a03c 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -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_BIGINT_ITEM_CELL.get()); }) .build()); } diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index 7341c99..fc6a2e5 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -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 INFINITY_BIGINT_ITEM_CELL = ITEMS.register( + "infinity_item_cell", InfinityBigIntegerCellItem::new + ); + + /** * 为 PartItem 注册 AE2 部件模型。 * 在客户端进行模型/几何体注册时调用。 diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java new file mode 100644 index 0000000..4ef9036 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -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); + } +} diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java new file mode 100644 index 0000000..e24a5ab --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -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 + *

+ * 世界级别的持久化容器,集中管理所有 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 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 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(); + } +} From 37d24334bb00dcc0051293b72188eb3262255192 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 16 Sep 2025 20:01:03 +0800 Subject: [PATCH 04/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Infinity=20?= =?UTF-8?q?=E5=AD=98=E5=82=A8=EF=BC=9A=E7=A7=BB=E9=99=A4=E5=8F=AF=E5=8F=98?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=20EMPTY=EF=BC=8C=E5=B0=81=E8=A3=85=20NBT=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=EF=BC=9B=E6=8C=89=E4=B8=96=E7=95=8C=E7=AE=A1?= =?UTF-8?q?=E7=90=86=20StorageManager=EF=BC=88=E7=A7=BB=E9=99=A4=E5=85=A8?= =?UTF-8?q?=E5=B1=80=20INSTANCE=EF=BC=89=EF=BC=9B=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E6=97=B6=E9=87=8D=E7=BD=AE=20totalStored=20=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=B4=AF=E8=AE=A1=EF=BC=9B=E7=94=A8=20Atomic?= =?UTF-8?q?Boolean=20=E4=BB=A3=E6=9B=BF=E9=98=9F=E5=88=97=20contains?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=AD=A3=20persist=20=E7=9A=84=E7=A9=BA/?= =?UTF-8?q?=E6=97=A0=E7=AE=A1=E7=90=86=E5=99=A8=E5=A4=84=E7=90=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfinityBigIntegerCellInventory.java | 49 ++++++++++++++----- .../util/storage/InfinityDataStorage.java | 32 ++++++++++-- .../util/storage/InfinityStorageManager.java | 36 +++++++++++--- 3 files changed, 93 insertions(+), 24 deletions(-) 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 19273c4..7e2216f 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 @@ -26,6 +26,7 @@ import java.math.RoundingMode; import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; /** * InfinityBigIntegerCellInventory @@ -91,9 +92,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return null; } - // 获取全局存储实例 + // 获取全局存储实例(尽量安全地获取任意已注册的世界实例) private static InfinityStorageManager getStorageInstance() { - return InfinityStorageManager.INSTANCE; + return InfinityStorageManager.getAnyInstance(); } // 服务器 tick 回调:合并并执行待持久化项 @@ -133,10 +134,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { // 如果没有UUID,返回空存储 - return InfinityDataStorage.EMPTY; + return InfinityDataStorage.empty(); } else { - // 否则获取或创建对应UUID的存储 - return getStorageInstance().getOrCreateCell(getUUID()); + // 否则获取或创建对应UUID的存储,若管理器不可用则返回空存储以避免 NPE + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) return InfinityDataStorage.empty(); + return mgr.getOrCreateCell(getUUID()); } } @@ -187,8 +190,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 if (!stack.hasTag()) return; - ListTag keys = this.getCellStorage().keys; - ListTag amounts = this.getCellStorage().amounts; + // 在加载前重置缓存,避免重复累计 + totalStored = BigInteger.ZERO; + InfinityDataStorage storage = this.getCellStorage(); + ListTag keys = storage.getKeys(); + ListTag amounts = storage.getAmounts(); int len = Math.min(keys.size(), amounts.size()); for (int i = 0; i < len; i++) { AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); @@ -223,6 +229,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 标记数据需要保存,并通知容器或直接持久化 + private final AtomicBoolean queued = new AtomicBoolean(false); + private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O isPersisted = false; @@ -231,7 +239,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { container.saveChanges(); } else { // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (!PENDING_PERSIST.contains(this)) { + if (queued.compareAndSet(false, true)) { PENDING_PERSIST.offer(this); } } @@ -258,13 +266,21 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (map.isEmpty()) { // 如果存储为空,移除UUID和全局存储中的数据 if (this.hasUUID()) { - getStorageInstance().removeCell(getUUID()); + InfinityStorageManager mgr = getStorageInstance(); + if (mgr != null) mgr.removeCell(getUUID()); if (stack.getTag() != null) { stack.getTag().remove("uuid"); // 移除缓存的 total 字段 stack.getTag().remove("total"); } + // 清理 queued 标志并标记已持久化 + queued.set(false); + isPersisted = true; + return; } + // 无 UUID 且为空,不需要做额外操作,但确保已标记为已持久化 + queued.set(false); + isPersisted = true; return; } // 构建要保存的Key和数量列表(混合表示:long 或 string) @@ -284,11 +300,16 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } // 如果没有Key,更新为空存储,否则保存数据 - if (keys.isEmpty()) { - getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + // 无可用存储管理器,跳过持久化(保留内存状态) } else { - // amounts 现在为 CompoundTag 列表 - getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); + if (keys.isEmpty()) { + mgr.updateCell(this.getUUID(), new InfinityDataStorage()); + } else { + // amounts 现在为 CompoundTag 列表 + mgr.modifyCell(this.getUUID(), keys, amountTags); + } } // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long if (stack.getOrCreateTag() != null) { @@ -299,6 +320,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } isPersisted = true; + // 持久化完成后,清除 queued 标志 + queued.set(false); } // 插入物品到存储单元 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 4ef9036..0a44d87 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -18,17 +18,17 @@ import net.minecraft.nbt.Tag; */ public class InfinityDataStorage { - /** 空实例(表示没有数据) */ - public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + // 不再暴露可变的共享实例,避免多个调用方修改同一 ListTag 导致交叉污染 + private static final InfinityDataStorage TRUE_EMPTY = new InfinityDataStorage(new ListTag(), new ListTag()); /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ - public ListTag keys; + private ListTag keys; /** * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 */ - public ListTag amounts; + private ListTag amounts; public InfinityDataStorage() { this(new ListTag(), new ListTag()); @@ -39,6 +39,14 @@ public class InfinityDataStorage { this.amounts = amounts; } + /** + * 返回一个空的不可共享实例(调用方若需要可变副本请自行复制) + */ + public static InfinityDataStorage empty() { + // 返回一个新的实例以避免共享可变对象被篡改 + return new InfinityDataStorage(new ListTag(), new ListTag()); + } + /** * 将当前数据封装为 CompoundTag 以写入存档 */ @@ -58,4 +66,20 @@ public class InfinityDataStorage { ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND); return new InfinityDataStorage(stackKeys, stackAmounts); } + + public ListTag getKeys() { + return keys; + } + + public ListTag getAmounts() { + return amounts; + } + + public void setKeys(ListTag keys) { + this.keys = keys; + } + + public void setAmounts(ListTag amounts) { + this.amounts = amounts; + } } 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 e24a5ab..1b36fbd 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,13 +2,16 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; import net.minecraft.world.level.saveddata.SavedData; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; /** * InfinityStorageManager @@ -26,9 +29,9 @@ public class InfinityStorageManager extends SavedData { */ public static final String FILE_NAME = "eap_infinity_biginteger_cells"; /** - * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) + * Per-world instances to avoid cross-world leakage. Keyed by world ResourceKey. */ - public static InfinityStorageManager INSTANCE = null; + private static final Map, InfinityStorageManager> INSTANCES = new ConcurrentHashMap<>(); /** * UUID -> 数据 的内存映射 */ @@ -54,10 +57,29 @@ public class InfinityStorageManager extends SavedData { * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE */ public static InfinityStorageManager getForLevel(ServerLevel level) { - if (INSTANCE == null && level != null) { - INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); + if (level == null) return null; + ResourceKey key = level.dimension(); + InfinityStorageManager mgr = INSTANCES.get(key); + if (mgr == null) { + mgr = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); + INSTANCES.put(key, mgr); } - return INSTANCE; + return mgr; + } + + /** + * 返回任何现有实例,作为无法访问 ServerLevel 的代码路径的安全回退。 + */ + public static InfinityStorageManager getAnyInstance() { + return INSTANCES.values().stream().findFirst().orElse(null); + } + + /** + * 删除世界的实例(在世界卸载时调用) + */ + public static void removeForLevel(ServerLevel level) { + if (level == null) return; + INSTANCES.remove(level.dimension()); } @Override @@ -98,8 +120,8 @@ public class InfinityStorageManager extends SavedData { public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { - cellToModify.keys = stackKeys; - cellToModify.amounts = stackAmounts; + cellToModify.setKeys(stackKeys); + cellToModify.setAmounts(stackAmounts); } updateCell(cellID, cellToModify); } From 3039add611562c1338c238c35052e903092ce4ba Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 16 Sep 2025 21:43:03 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E9=99=90=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E3=80=81=E9=85=8D=E6=96=B9=E7=AD=89=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae/items/InfinityBigIntegerCellItem.java | 2 ++ .../extendedae_plus/init/ModCreativeTabs.java | 2 +- .../com/extendedae_plus/init/ModItems.java | 4 ++-- .../assets/extendedae_plus/lang/en_us.json | 2 ++ .../assets/extendedae_plus/lang/zh_cn.json | 2 ++ .../models/item/infinity_biginteger_cell.json | 8 ++++++++ .../item/infinity_biginteger_cell.png | Bin 0 -> 2266 bytes .../recipes/infinity_biginteger_cell.json | 18 ++++++++++++++++++ 8 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png create mode 100644 src/main/resources/data/extendedae_plus/recipes/infinity_biginteger_cell.json 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 5b550a1..7cfb313 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -36,6 +36,8 @@ public class InfinityBigIntegerCellItem extends Item { @Nullable Level world, @NotNull List tooltip, @NotNull TooltipFlag context) { + tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon")); + Preconditions.checkArgument(stack.getItem() == this); // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑 CompoundTag tag = stack.getTag(); diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index dc4a03c..f30b2d6 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -34,7 +34,7 @@ public final class ModCreativeTabs { output.accept(ModItems.createEntitySpeedCardStack(8)); output.accept(ModItems.createEntitySpeedCardStack(16)); - output.accept(ModItems.INFINITY_BIGINT_ITEM_CELL.get()); + output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get()); }) .build()); } diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index fc6a2e5..6ec6a70 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -66,8 +66,8 @@ public final class ModItems { () -> new EntitySpeedCardItem(new Item.Properties()) ); - public static final RegistryObject INFINITY_BIGINT_ITEM_CELL = ITEMS.register( - "infinity_item_cell", InfinityBigIntegerCellItem::new + public static final RegistryObject INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register( + "infinity_biginteger_cell", InfinityBigIntegerCellItem::new ); diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 230dade..8ae2ff7 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -22,6 +22,8 @@ "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": "§cI§6n§ef§ai§bn§di§9t§fy §cS§6t§eo§ar§ba§dg§9e §fC§co§6m§ep§ao§bn§de§9n§ft", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon": "§7Through the sacrifice of nine rare materials, you summon Iava, who bestows upon the summoner an §cI§6n§ef§ai§bn§di§9t§fy §cS§6t§eo§ar§ba§dg§9e §fC§co§6m§ep§ao§bn§de§9n§ft§7 forged from the endless void.", "tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s", "tooltip.extendedae_plus.entity_speed_card.max": "Max effective: %s x", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index dd25863..b5f663b 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -22,6 +22,8 @@ "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": "§c无§6限§e存§a储§b元§d件", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon": "§7以九种稀有材料为祭,唤出 Iava;其将一枚 §c无§6限§e存§a储§b元§d件 §7赐予召唤者,源自无尽虚空。", "tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s", "tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍", diff --git a/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json b/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json new file mode 100644 index 0000000..235c138 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json @@ -0,0 +1,8 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "extendedae_plus:item/infinity_biginteger_cell" + } +} + + diff --git a/src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png b/src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png new file mode 100644 index 0000000000000000000000000000000000000000..5c47ca4fdedf33c73a8afa63e5c1fe41f4978db7 GIT binary patch literal 2266 zcmcgueNYr-99|d)5hWog8wPb}Q}ctpyWQKp+uJx1Iq)zJ5GcSgb8q)O?)2_<-Cf`y zrqGNeq%2eOqiIT;v`;ll#}dgAF`Ud8ok%OgR2-eeufnvbcaP)320z9>x|zLw_uc1x zp6BJhA1G3_uoqzvHp z*sEnwSe;wMS1;f#0+u!vO%1S+zz0+g4fwo%g$-D-ATJBY+O!@+gAjFr6-(Cy(Go{K znjy*nwde?(HyB9NY|)t+r`cp=Qc%)B(s~1_CkULdu!Mm%Flgw9K{;7)u?4oQkQltO zVs2HHSiQcovQk%R)QNJLp0HRf8V5<@5P>UIew7R0e&ykQ1{+X#*(0eQ(T{43oKq}U ztr)c07lTg<)B2TAo?yoG0Z!5rI)fHd5Ge3roK!A*gW>|O2VURKx6lTH0t`))S~mw9gtdDVSh}jv^HB&E zhDKfw8te&{M1S3f?W+&4h`atW0Hsifm52b~7bN(^_ zcTPQa@4?(%U-r5DAA`|r`(Hph&!FvPtR#?d36x^`2Vfa-6FTyVy;D>pV;j2Vh zH+?vQM4ZdEr57=c@2p63C(KH2P184;M$1Q%N4f1AIv?D;eDat33*#Q$^klQPqsB=j@t2le0pTmq^P9W`ZM0yc}eTAs@x`N?y(kg?@v89 zs?wg>)16Q-v$t#MkGec5FY?cg*{__pe%d*=WrTTr^YiuD*MA|7HBRVRy?i(N_UcQQ z_bq#G_YXS{b{?WGb;Rvh<^QB@eKb-*MxMRmY3n+%aDH>tytghM7#crx=VUSGo0GeA z-|oIqlT|D1-#9zv2KvX|NcwUlI^<{OzB&$(To+@m*Q^!xk6S}Lu&?Iz)4zS3iu~R& zBC4<;;{KHpV})z&CG)nlcF#FwKaq&kani3>Q<7RY9-H1(8r!x0IZ#TiEw|;)Xe!M( zrPhrcQ;=2L+AuPa8Ij+9ZdqW$(MQGx*txCSQs#V@c}hi^jy{H@G*!o+%|XU(IsKLU zrFe1a{H-}pr`CbenOA#SV&07C$cw4%N$oy^F2Y}aan`mswlDU!(NjMxK76r4mig??bH%wcSIOZT0_w|s^mL;@A$L<|df4n!M zxj1S2wdmSb*=%3586gg}C2m-<=n_OJ*GG0{uI;1g^A`UDbnOYi literal 0 HcmV?d00001 diff --git a/src/main/resources/data/extendedae_plus/recipes/infinity_biginteger_cell.json b/src/main/resources/data/extendedae_plus/recipes/infinity_biginteger_cell.json new file mode 100644 index 0000000..a05271b --- /dev/null +++ b/src/main/resources/data/extendedae_plus/recipes/infinity_biginteger_cell.json @@ -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 + } +} \ No newline at end of file From d316c5f0393bfd148349c72277c5e3fa5f8b40e0 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 16 Sep 2025 22:02:13 +0800 Subject: [PATCH 06/10] =?UTF-8?q?fix=EF=BC=9A=E5=9B=9E=E9=80=80=E6=97=A0?= =?UTF-8?q?=E9=99=90=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfinityBigIntegerCellInventory.java | 49 +++++-------------- .../util/storage/InfinityDataStorage.java | 32 ++---------- .../util/storage/InfinityStorageManager.java | 36 +++----------- 3 files changed, 24 insertions(+), 93 deletions(-) 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 7e2216f..19273c4 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 @@ -26,7 +26,6 @@ import java.math.RoundingMode; import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; /** * InfinityBigIntegerCellInventory @@ -92,9 +91,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return null; } - // 获取全局存储实例(尽量安全地获取任意已注册的世界实例) + // 获取全局存储实例 private static InfinityStorageManager getStorageInstance() { - return InfinityStorageManager.getAnyInstance(); + return InfinityStorageManager.INSTANCE; } // 服务器 tick 回调:合并并执行待持久化项 @@ -134,12 +133,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { // 如果没有UUID,返回空存储 - return InfinityDataStorage.empty(); + return InfinityDataStorage.EMPTY; } else { - // 否则获取或创建对应UUID的存储,若管理器不可用则返回空存储以避免 NPE - InfinityStorageManager mgr = getStorageInstance(); - if (mgr == null) return InfinityDataStorage.empty(); - return mgr.getOrCreateCell(getUUID()); + // 否则获取或创建对应UUID的存储 + return getStorageInstance().getOrCreateCell(getUUID()); } } @@ -190,11 +187,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 if (!stack.hasTag()) return; - // 在加载前重置缓存,避免重复累计 - totalStored = BigInteger.ZERO; - InfinityDataStorage storage = this.getCellStorage(); - ListTag keys = storage.getKeys(); - ListTag amounts = storage.getAmounts(); + 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)); @@ -229,8 +223,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 标记数据需要保存,并通知容器或直接持久化 - private final AtomicBoolean queued = new AtomicBoolean(false); - private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O isPersisted = false; @@ -239,7 +231,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { container.saveChanges(); } else { // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (queued.compareAndSet(false, true)) { + if (!PENDING_PERSIST.contains(this)) { PENDING_PERSIST.offer(this); } } @@ -266,21 +258,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (map.isEmpty()) { // 如果存储为空,移除UUID和全局存储中的数据 if (this.hasUUID()) { - InfinityStorageManager mgr = getStorageInstance(); - if (mgr != null) mgr.removeCell(getUUID()); + getStorageInstance().removeCell(getUUID()); if (stack.getTag() != null) { stack.getTag().remove("uuid"); // 移除缓存的 total 字段 stack.getTag().remove("total"); } - // 清理 queued 标志并标记已持久化 - queued.set(false); - isPersisted = true; - return; } - // 无 UUID 且为空,不需要做额外操作,但确保已标记为已持久化 - queued.set(false); - isPersisted = true; return; } // 构建要保存的Key和数量列表(混合表示:long 或 string) @@ -300,16 +284,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } // 如果没有Key,更新为空存储,否则保存数据 - InfinityStorageManager mgr = getStorageInstance(); - if (mgr == null) { - // 无可用存储管理器,跳过持久化(保留内存状态) + if (keys.isEmpty()) { + getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); } else { - if (keys.isEmpty()) { - mgr.updateCell(this.getUUID(), new InfinityDataStorage()); - } else { - // amounts 现在为 CompoundTag 列表 - mgr.modifyCell(this.getUUID(), keys, amountTags); - } + // amounts 现在为 CompoundTag 列表 + getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); } // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long if (stack.getOrCreateTag() != null) { @@ -320,8 +299,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } } isPersisted = true; - // 持久化完成后,清除 queued 标志 - queued.set(false); } // 插入物品到存储单元 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 0a44d87..4ef9036 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -18,17 +18,17 @@ import net.minecraft.nbt.Tag; */ public class InfinityDataStorage { - // 不再暴露可变的共享实例,避免多个调用方修改同一 ListTag 导致交叉污染 - private static final InfinityDataStorage TRUE_EMPTY = new InfinityDataStorage(new ListTag(), new ListTag()); + /** 空实例(表示没有数据) */ + public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ - private ListTag keys; + public ListTag keys; /** * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 */ - private ListTag amounts; + public ListTag amounts; public InfinityDataStorage() { this(new ListTag(), new ListTag()); @@ -39,14 +39,6 @@ public class InfinityDataStorage { this.amounts = amounts; } - /** - * 返回一个空的不可共享实例(调用方若需要可变副本请自行复制) - */ - public static InfinityDataStorage empty() { - // 返回一个新的实例以避免共享可变对象被篡改 - return new InfinityDataStorage(new ListTag(), new ListTag()); - } - /** * 将当前数据封装为 CompoundTag 以写入存档 */ @@ -66,20 +58,4 @@ public class InfinityDataStorage { ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND); return new InfinityDataStorage(stackKeys, stackAmounts); } - - public ListTag getKeys() { - return keys; - } - - public ListTag getAmounts() { - return amounts; - } - - public void setKeys(ListTag keys) { - this.keys = keys; - } - - public void setAmounts(ListTag amounts) { - this.amounts = amounts; - } } 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 1b36fbd..e24a5ab 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,16 +2,13 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; import net.minecraft.world.level.saveddata.SavedData; import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; /** * InfinityStorageManager @@ -29,9 +26,9 @@ public class InfinityStorageManager extends SavedData { */ public static final String FILE_NAME = "eap_infinity_biginteger_cells"; /** - * Per-world instances to avoid cross-world leakage. Keyed by world ResourceKey. + * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) */ - private static final Map, InfinityStorageManager> INSTANCES = new ConcurrentHashMap<>(); + public static InfinityStorageManager INSTANCE = null; /** * UUID -> 数据 的内存映射 */ @@ -57,29 +54,10 @@ public class InfinityStorageManager extends SavedData { * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE */ public static InfinityStorageManager getForLevel(ServerLevel level) { - if (level == null) return null; - ResourceKey key = level.dimension(); - InfinityStorageManager mgr = INSTANCES.get(key); - if (mgr == null) { - mgr = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); - INSTANCES.put(key, mgr); + if (INSTANCE == null && level != null) { + INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); } - return mgr; - } - - /** - * 返回任何现有实例,作为无法访问 ServerLevel 的代码路径的安全回退。 - */ - public static InfinityStorageManager getAnyInstance() { - return INSTANCES.values().stream().findFirst().orElse(null); - } - - /** - * 删除世界的实例(在世界卸载时调用) - */ - public static void removeForLevel(ServerLevel level) { - if (level == null) return; - INSTANCES.remove(level.dimension()); + return INSTANCE; } @Override @@ -120,8 +98,8 @@ public class InfinityStorageManager extends SavedData { public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { - cellToModify.setKeys(stackKeys); - cellToModify.setAmounts(stackAmounts); + cellToModify.keys = stackKeys; + cellToModify.amounts = stackAmounts; } updateCell(cellID, cellToModify); } From fb8db3693b1185a7ad0d0369ee0b543e6deea3a3 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Wed, 17 Sep 2025 15:14:10 +0800 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=97=A0?= =?UTF-8?q?=E7=BA=BF=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E7=9A=84=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E7=A7=8D=E7=B1=BB=E6=95=B0=E9=87=8F=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=9B=E4=BF=AE=E6=94=B9=E5=90=8D=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../storage/InfinityBigIntegerCellInventory.java | 3 +++ .../ae/items/InfinityBigIntegerCellItem.java | 14 +++++++++++++- .../assets/extendedae_plus/lang/en_us.json | 3 ++- .../assets/extendedae_plus/lang/zh_cn.json | 5 +++-- 4 files changed, 21 insertions(+), 4 deletions(-) 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 19273c4..8642326 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 @@ -297,6 +297,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } else { stack.getOrCreateTag().putString("total", totalStored.toString()); } + // 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示 + int typesCount = this.getCellStoredMap().size(); + stack.getOrCreateTag().putInt("types", typesCount); } isPersisted = true; } 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 7cfb313..dde5b96 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -36,7 +36,8 @@ public class InfinityBigIntegerCellItem extends Item { @Nullable Level world, @NotNull List tooltip, @NotNull TooltipFlag context) { - tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon")); + 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,避免触发持久化或加载逻辑 @@ -46,6 +47,17 @@ public class InfinityBigIntegerCellItem extends Item { 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; diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 8ae2ff7..1245bae 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -23,7 +23,8 @@ "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": "§cI§6n§ef§ai§bn§di§9t§fy §cS§6t§eo§ar§ba§dg§9e §fC§co§6m§ep§ao§bn§de§9n§ft", - "tooltip.extendedae_plus.infinity_biginteger_cell.summon": "§7Through the sacrifice of nine rare materials, you summon Iava, who bestows upon the summoner an §cI§6n§ef§ai§bn§di§9t§fy §cS§6t§eo§ar§ba§dg§9e §fC§co§6m§ep§ao§bn§de§9n§ft§7 forged from the endless void.", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§7Through the sacrifice of nine rare materials, you summon Iava, who grants the summoner an §4all-consuming§csilence§6.", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4Within §ca §6small §espace§a, §bthere §dare §4a §cthousand §6realms", "tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s", "tooltip.extendedae_plus.entity_speed_card.max": "Max effective: %s x", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index b5f663b..7f94f24 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -22,8 +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": "§c无§6限§e存§a储§b元§d件", - "tooltip.extendedae_plus.infinity_biginteger_cell.summon": "§7以九种稀有材料为祭,唤出 Iava;其将一枚 §c无§6限§e存§a储§b元§d件 §7赐予召唤者,源自无尽虚空。", + "item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e物§a的§b寂§d静", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "九重献祭, 终得虚空回响——觐见虚空之主Iava, 赐汝§4吞§c噬§6万§e籁§a的§b寂§d静", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§c寸§6之§e间§a, §b自§d有§4千§c寰", "tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s", "tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍", From 01e77aff08aeebe7df4778e3ae300b001a430640 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Wed, 17 Sep 2025 18:14:36 +0800 Subject: [PATCH 08/10] 1.4.2-beta --- CHANGELOG.md | 47 +++++++++++++++++++ gradle.properties | 2 +- .../assets/extendedae_plus/lang/en_us.json | 6 +-- .../assets/extendedae_plus/lang/zh_cn.json | 6 +-- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa268f8..a056e01 100644 --- a/CHANGELOG.md +++ b/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 书签优先用于编码样板匹配(书签越靠前,匹配优先级越高) diff --git a/gradle.properties b/gradle.properties index 2524cfb..d7ffbbb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 1245bae..2187081 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -22,9 +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": "§cI§6n§ef§ai§bn§di§9t§fy §cS§6t§eo§ar§ba§dg§9e §fC§co§6m§ep§ao§bn§de§9n§ft", - "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§7Through the sacrifice of nine rare materials, you summon Iava, who grants the summoner an §4all-consuming§csilence§6.", - "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4Within §ca §6small §espace§a, §bthere §dare §4a §cthousand §6realms", + "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", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 7f94f24..d89594e 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -22,9 +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": "九重献祭, 终得虚空回响——觐见虚空之主Iava, 赐汝§4吞§c噬§6万§e籁§a的§b寂§d静", - "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§c寸§6之§e间§a, §b自§d有§4千§c寰", + "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 倍", From ef83089dc38b25305a3a0a1a0c382ce8f128f6b5 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Thu, 18 Sep 2025 00:04:25 +0800 Subject: [PATCH 09/10] =?UTF-8?q?fix:=20=E6=97=A0=E7=BA=BF=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E5=85=83=E4=BB=B6=E4=BD=BF=E7=94=A8=E9=A5=B1=E5=92=8C?= =?UTF-8?q?=20BigInteger=20=E7=B4=AF=E5=8A=A0=EF=BC=8C=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=20long=20=E6=BA=A2=E5=87=BA=E5=AF=BC=E8=87=B4=20AE=20=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfinityBigIntegerCellInventory.java | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) 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 8642326..d46cf2d 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 @@ -240,12 +240,40 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取所有可用的物品堆栈及其数量 @Override public void getAvailableStacks(KeyCounter out) { + // 使用饱和(saturating)加法将 BigInteger 值转换为 long 并安全地累加到 KeyCounter 中。 + // 问题背景:当同一物品存在于多个 cell 中,AE2 的 KeyCounter 使用 long 来记录数量, + // 若简单将单个 cell 的超长值截断为 Long.MAX_VALUE 并直接 add,多个 cell 的合并会导致 + // 原本代表 "大于 long" 的值被重复添加而导致读取异常。解决方案:每次向 KeyCounter 添加前, + // 先读取当前计数器中的已有值(long),并使用 BigInteger 做饱和加法后再写回为 long,避免中间溢出。 + BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); Object2ObjectMap map = getCellStoredMap(); for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { - BigInteger v = entry.getValue(); - long toAdd = v.compareTo(maxLong) > 0 ? Long.MAX_VALUE : v.longValue(); - out.add(entry.getKey(), toAdd); + 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); + } } } From 1490f73b5f261aac7b38f00772ff6bc29855a7d8 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Thu, 18 Sep 2025 14:32:38 +0800 Subject: [PATCH 10/10] =?UTF-8?q?fix:=20=E4=BF=9D=E8=AF=81=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E6=97=B6=E5=BC=BA=E5=88=B6=E6=8C=81=E4=B9=85=E5=8C=96?= =?UTF-8?q?=E6=97=A0=E9=99=90=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/ExtendedAEPlus.java | 4 ++ .../InfinityBigIntegerCellInventory.java | 43 +++++++++++-------- .../extendedae_plus/ae/client/gui/Icon.java | 33 -------------- 3 files changed, 29 insertions(+), 51 deletions(-) delete mode 100644 src/main/java/com/extendedae_plus/ae/client/gui/Icon.java diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 6a84267..25d675e 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,6 +3,7 @@ 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.*; @@ -54,6 +55,9 @@ 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); } 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 d46cf2d..2cbb417 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 @@ -17,8 +17,8 @@ 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.common.MinecraftForge; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import java.math.BigDecimal; import java.math.BigInteger; @@ -27,6 +27,7 @@ import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** * InfinityBigIntegerCellInventory *

@@ -50,15 +51,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 数字格式化对象,保留两位小数(复用以减少对象分配) private static final DecimalFormat DF = new DecimalFormat("#.##"); - static { - // 在类加载时注册服务器 tick 监听器,用于在主线程合并写入 - try { - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); - } catch (Throwable ignored) { - // 保守降级:若注册失败,不阻塞实例化 - } - } - // 关联的 ItemStack(含可能的 uuid NBT) private final ItemStack stack; // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 @@ -97,7 +89,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 服务器 tick 回调:合并并执行待持久化项 - private static void onServerTick(TickEvent.ServerTickEvent event) { + public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END) return; InfinityBigIntegerCellInventory inv; // 处理本次 tick 中的全部待持久化项 @@ -107,11 +99,32 @@ public class InfinityBigIntegerCellInventory implements StorageCell { 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 通常在主线程运行 @@ -240,12 +253,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取所有可用的物品堆栈及其数量 @Override public void getAvailableStacks(KeyCounter out) { - // 使用饱和(saturating)加法将 BigInteger 值转换为 long 并安全地累加到 KeyCounter 中。 - // 问题背景:当同一物品存在于多个 cell 中,AE2 的 KeyCounter 使用 long 来记录数量, - // 若简单将单个 cell 的超长值截断为 Long.MAX_VALUE 并直接 add,多个 cell 的合并会导致 - // 原本代表 "大于 long" 的值被重复添加而导致读取异常。解决方案:每次向 KeyCounter 添加前, - // 先读取当前计数器中的已有值(long),并使用 BigInteger 做饱和加法后再写回为 long,避免中间溢出。 - BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); Object2ObjectMap map = getCellStoredMap(); for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { diff --git a/src/main/java/com/extendedae_plus/ae/client/gui/Icon.java b/src/main/java/com/extendedae_plus/ae/client/gui/Icon.java deleted file mode 100644 index 9574df3..0000000 --- a/src/main/java/com/extendedae_plus/ae/client/gui/Icon.java +++ /dev/null @@ -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); - } -}