From f60629aadfb75ca2f748f6fdd04e4dcd210339b7 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 27 Sep 2025 20:18:13 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E8=AF=B7=E6=B1=82=E9=87=8Fpop?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixin/ae2/autopattern/CraftingTreeNodeMixin.java | 7 ------- .../mixin/ae2/autopattern/CraftingTreeProcessMixin.java | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java index b2651d5..23d0102 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java @@ -20,11 +20,4 @@ public class CraftingTreeNodeMixin { // push the requestedAmount before addContainerItems is called RequestedAmountHolder.push(requestedAmount); } - - @Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V", - at = @At(value = "RETURN")) - private void clearRequestedAmountOnReturn(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) { - // pop the pushed requested amount on return - RequestedAmountHolder.pop(); - } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java index 68e510e..fb0d122 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java @@ -55,6 +55,7 @@ public abstract class CraftingTreeProcessMixin { CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode; AEKey parentTarget = parentAcc.eap$getWhat(); long requested = RequestedAmountHolder.get(); + RequestedAmountHolder.pop(); // 根据配置决定是否在 provider 间轮询分配请求量(默认开启) long perProvider = 1L; From a95389e5fa0c36a8990020e9ef1b144d8450ad0c Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 27 Sep 2025 20:22:57 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=85=81=E8=AE=B8=E6=97=A0?= =?UTF-8?q?=E9=99=90=E7=9B=98=E4=B8=AD=E6=94=BE=E5=85=A5=E7=A9=BA=E7=9A=84?= =?UTF-8?q?=E6=97=A0=E9=99=90=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae/api/storage/InfinityBigIntegerCellInventory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 675c41e..f050184 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 @@ -343,7 +343,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return 0; } // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { + if (what instanceof AEItemKey itemKey && + itemKey.getItem() instanceof InfinityBigIntegerCellItem && + itemKey.get(DataComponents.CUSTOM_DATA) != null + ) { return 0; } From f9108851903b77b3529e308abea3605b13103c69 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 27 Sep 2025 20:59:17 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E9=87=8D=E6=96=B0=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E5=AE=9E=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BB=93=E6=9E=84=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7=EF=BC=9B=E6=B7=BB=E5=8A=A0=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=E5=99=A8=E5=AF=B9appflux=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E7=94=B5=E9=87=8F=E7=9A=84=E6=89=A3=E9=99=A4=E6=94=AF=E6=8C=81?= =?UTF-8?q?=EF=BC=9B=E6=B7=BB=E5=8A=A0=E5=AE=9E=E4=BD=93=E5=8A=A0=E9=80=9F?= =?UTF-8?q?=E5=99=A8=E6=98=AF=E5=90=A6=E4=BC=98=E5=85=88=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=A3=81=E7=9B=98=E5=AD=98=E5=82=A8=E8=83=BD=E6=BA=90=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae/menu/EntitySpeedTickerMenu.java | 242 +++++++++--------- .../ae/parts/EntitySpeedTickerPart.java | 238 ++++++++++------- .../ae/screen/EntitySpeedTickerScreen.java | 142 +++++----- .../extendedae_plus/config/ModConfigs.java | 7 + .../util/FluxEnergyHelper.java | 41 +++ .../com/extendedae_plus/util/PowerUtils.java | 162 ------------ .../{ => entitySpeed}/ConfigParsingUtils.java | 99 ++++--- .../util/entitySpeed/PowerUtils.java | 98 +++++++ .../assets/extendedae_plus/lang/en_us.json | 1 + .../assets/extendedae_plus/lang/zh_cn.json | 1 + 10 files changed, 543 insertions(+), 488 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java delete mode 100644 src/main/java/com/extendedae_plus/util/PowerUtils.java rename src/main/java/com/extendedae_plus/util/{ => entitySpeed}/ConfigParsingUtils.java (68%) create mode 100644 src/main/java/com/extendedae_plus/util/entitySpeed/PowerUtils.java diff --git a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java index 40e1e28..086a261 100644 --- a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java +++ b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java @@ -9,165 +9,167 @@ import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen; import com.extendedae_plus.config.ModConfigs; import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModMenuTypes; -import com.extendedae_plus.util.ConfigParsingUtils; -import com.extendedae_plus.util.PowerUtils; +import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils; +import com.extendedae_plus.util.entitySpeed.PowerUtils; import it.unimi.dsi.fastutil.shorts.ShortSet; import net.minecraft.client.Minecraft; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.level.block.Block; +import net.minecraft.world.inventory.Slot; import net.minecraft.world.level.block.entity.BlockEntity; -// 实体加速器菜单,负责与客户端界面同步数据 +/** + * 实体加速器菜单,负责管理客户端与服务端的数据同步,处理加速卡、能量卡和目标方块的状态。 + */ public class EntitySpeedTickerMenu extends UpgradeableMenu { - @GuiSync(716) - public boolean accelerateEnabled = true; - // 已安装的实体加速卡数量(用于能耗计算) - @GuiSync(717) - public int entitySpeedCardCount; - // 已安装的能量卡数量 - @GuiSync(718) - public int energyCardCount; - // 当前生效的配置倍率(从配置中读取并同步) - // 当前计算出的生效速度(product of multipliers),同步给客户端用于显示 - @GuiSync(719) - public int effectiveSpeed = 1; - @GuiSync(720) - public double multiplier = 1.0; - @GuiSync(721) - public boolean targetBlacklisted = false; - // 来自部件的网络能量充足提示(服务端设置,客户端用于显示警告) - @GuiSync(722) public boolean networkEnergySufficient = true; + @GuiSync(716) public boolean accelerateEnabled = true; // 是否启用加速 + @GuiSync(717) public int entitySpeedCardCount; // 已安装的实体加速卡数量 + @GuiSync(718) public int energyCardCount; // 已安装的能量卡数量 + @GuiSync(719) public int effectiveSpeed = 1; // 当前生效的加速倍率 + @GuiSync(720) public double multiplier = 1.0; // 目标方块的配置倍率 + @GuiSync(721) public boolean targetBlacklisted = false; // 目标方块是否在黑名单中 + @GuiSync(722) public boolean networkEnergySufficient = true; // 网络能量是否充足 + /** + * 构造函数,初始化菜单并绑定部件。 + * @param id 菜单ID + * @param ip 玩家背包 + * @param host 关联的实体加速器部件 + */ + public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) { + super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host); + if (host != null) { + host.menu = this; // 绑定菜单到部件 + this.accelerateEnabled = host.getAccelerateEnabled(); // 同步初始开关状态 + } + } + /** + * 获取加速开关状态。 + * @return 是否启用加速 + */ public boolean getAccelerateEnabled() { return this.accelerateEnabled; } + /** + * 设置加速开关状态,并同步到部件。 + * @param enabled 是否启用加速 + */ public void setAccelerateEnabled(boolean enabled) { this.accelerateEnabled = enabled; + if (getHost() != null) { + getHost().setAccelerateEnabled(enabled); // 同步到部件 + } + broadcastChanges(); // 广播状态变化 } /** - * 从 Part 更新 networkEnergySufficient 的封装方法(由服务器调用) - * 该方法会更新 @GuiSync 字段并广播变化到客户端 + * 更新网络能量充足状态并广播到客户端。 + * @param sufficient 是否能量充足 */ public void setNetworkEnergySufficient(boolean sufficient) { this.networkEnergySufficient = sufficient; - // 触发一次数据广播,使客户端立即接收到最新状态 - this.broadcastChanges(); - } - - - // 构造方法,初始化菜单并与部件绑定 - public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) { - super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host); - // 让部件持有当前菜单实例,便于通信 - getHost().menu = this; - // 初始同步部件上的开关状态到菜单(服务器端构造时保证一致) - try { - this.accelerateEnabled = getHost().getAccelerateEnabled(); - } catch (Exception ignored) {} + broadcastChanges(); } + /** + * 服务端数据同步到客户端时调用,更新卡数量、目标状态和生效速度。 + */ @Override public void onServerDataSync(ShortSet updatedFields) { super.onServerDataSync(updatedFields); - // 重新统计实体加速卡和能量卡数量 - this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get()); - this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); - - // 计算当前面向方块的倍率(服务器端),并同步给客户端 - double mult = 1.0; - try { - BlockEntity target = getHost().getLevel().getBlockEntity( - getHost().getBlockEntity().getBlockPos().relative(getHost().getSide()) - ); - if (target != null) { - String blockId = BuiltInRegistries.BLOCK.getKey(target.getBlockState().getBlock()).toString(); - for (ConfigParsingUtils.MultiplierEntry me : - ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) { - if (me.pattern.matcher(blockId).matches()) { - mult = Math.max(mult, me.multiplier); - } - } - } - } catch (Exception ignored) { - } - this.multiplier = mult; - - // 检查目标是否在黑名单中,如果是则标记并将生效速度设为 0(服务器端计算) - boolean blacklisted = false; - try { - BlockEntity target = getHost().getLevel().getBlockEntity( - getHost().getBlockEntity().getBlockPos().relative(getHost().getSide()) - ); - if (target != null) { - Block block = target.getBlockState().getBlock(); - String blockId = BuiltInRegistries.BLOCK.getKey(block).toString(); // 直接拿到 "minecraft:stone" - - for (java.util.regex.Pattern p : ConfigParsingUtils.getCachedBlacklist( - ModConfigs.ENTITY_TICKER_BLACK_LIST.get())) { - if (p.matcher(blockId).matches()) { - blacklisted = true; - break; - } - } - } - } catch (Exception ignored) { - } - this.targetBlacklisted = blacklisted; - - // 计算生效速度:如果被黑名单则为 0,否则进行正常计算(使用工具类从菜单直接计算 product with cap,最多 8 张) - if (this.targetBlacklisted) { - this.effectiveSpeed = 0; - } else { - this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8); - } - - // 从部件同步网络能量充足状态:仅在服务器端从部件读取,客户端应使用由 @GuiSync 同步过来的值 - try { - EntitySpeedTickerPart host = getHost(); - if (host != null && !isClientSide()) { - this.networkEnergySufficient = host.isNetworkEnergySufficient(); - } - } catch (Exception ignored) {} - - // 如果在客户端,刷新界面 + updateCardCounts(); // 更新卡数量 + updateTargetStatus(); // 更新目标方块的黑名单和倍率 + updateEffectiveSpeed(); // 计算生效速度 + updateNetworkEnergyStatus(); // 同步能量状态 if (isClientSide()) { - if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) { - screen.refreshGui(); - } + refreshClientGui(); // 客户端刷新界面 } } - // 当任意槽位发生变化时调用 + /** + * 当槽位内容变化时调用,客户端更新卡数量和生效速度。 + * @param slot 发生变化的槽位 + */ @Override - public void onSlotChange(net.minecraft.world.inventory.Slot slot) { + public void onSlotChange(Slot slot) { super.onSlotChange(slot); - // 客户端重新统计卡数量并刷新界面 if (isClientSide()) { - this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get()); - this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); - // 立即在客户端计算生效速度以便界面即时反馈(使用与服务端相同的工具方法,最多 8 张卡) - this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8); - if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) { - screen.refreshGui(); - } + updateCardCounts(); + updateEffectiveSpeed(); + refreshClientGui(); } } + /** + * 广播数据变化,清理未启用槽位的显示堆栈。 + */ @Override public void broadcastChanges() { - // 遍历所有槽位,清理未启用但有物品显示的 OptionalFakeSlot for (Object o : this.slots) { - if (o instanceof OptionalFakeSlot fs) { - if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) { - fs.clearStack(); - } + if (o instanceof OptionalFakeSlot fs && !fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) { + fs.clearStack(); // 清理未启用槽位的显示 } } - // 调用标准的同步方法,通知监听者数据已更新 - this.standardDetectAndSendChanges(); + standardDetectAndSendChanges(); + } + + /** + * 更新加速卡和能量卡的数量。 + */ + private void updateCardCounts() { + this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get()); + this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); + } + + /** + * 更新目标方块的黑名单状态和倍率。 + */ + private void updateTargetStatus() { + BlockEntity target = getTargetBlockEntity(); + if (target == null) { + this.multiplier = 1.0; + this.targetBlacklisted = false; + return; + } + String blockId = net.minecraft.core.registries.BuiltInRegistries.BLOCK.getKey(target.getBlockState().getBlock()).toString(); + this.multiplier = ConfigParsingUtils.getMultiplierForBlock(blockId, ModConfigs.ENTITY_TICKER_MULTIPLIERS.get()); + this.targetBlacklisted = ConfigParsingUtils.isBlockBlacklisted(blockId, ModConfigs.ENTITY_TICKER_BLACK_LIST.get()); + } + + /** + * 计算生效速度(考虑黑名单和卡数量)。 + */ + private void updateEffectiveSpeed() { + this.effectiveSpeed = targetBlacklisted ? 0 : (int) PowerUtils.computeProductWithCap(getUpgrades(), 8); + } + + /** + * 同步网络能量状态(仅服务端)。 + */ + private void updateNetworkEnergyStatus() { + if (!isClientSide() && getHost() != null) { + this.networkEnergySufficient = getHost().isNetworkEnergySufficient(); + } + } + + /** + * 客户端刷新界面。 + */ + private void refreshClientGui() { + if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) { + screen.refreshGui(); + } + } + + /** + * 获取目标方块实体。 + * @return 目标方块实体或 null + */ + private BlockEntity getTargetBlockEntity() { + return getHost() != null ? + getHost().getLevel().getBlockEntity( + getHost().getBlockEntity().getBlockPos().relative(getHost().getSide()) + ) : null; } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java b/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java index a6fa886..293d576 100644 --- a/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java +++ b/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java @@ -4,12 +4,15 @@ import appeng.api.config.Actionable; import appeng.api.config.PowerMultiplier; import appeng.api.networking.GridFlags; import appeng.api.networking.IGridNode; +import appeng.api.networking.energy.IEnergyService; +import appeng.api.networking.security.IActionSource; import appeng.api.networking.ticking.IGridTickable; import appeng.api.networking.ticking.TickRateModulation; import appeng.api.networking.ticking.TickingRequest; import appeng.api.parts.IPartCollisionHelper; import appeng.api.parts.IPartItem; import appeng.api.parts.IPartModel; +import appeng.api.storage.MEStorage; import appeng.api.upgrades.IUpgradeableObject; import appeng.core.definitions.AEItems; import appeng.items.parts.PartModels; @@ -22,9 +25,8 @@ import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu; import com.extendedae_plus.config.ModConfigs; import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModMenuTypes; -import com.extendedae_plus.util.ConfigParsingUtils; -import com.extendedae_plus.util.PowerUtils; -import net.minecraft.core.BlockPos; +import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils; +import com.extendedae_plus.util.entitySpeed.PowerUtils; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -36,11 +38,11 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.phys.Vec3; +import net.neoforged.fml.ModList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.regex.Pattern; +import java.lang.reflect.Method; /** * EntitySpeedTickerPart 是一个可升级的 AE2 部件

@@ -63,24 +65,22 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel")); } + public EntitySpeedTickerMenu menu; // 当前打开的菜单实例 + private boolean accelerateEnabled = true; // 是否启用加速 + private boolean networkEnergySufficient = true; // 网络能量是否充足 + + /** - * 构造函数,初始化部件并设置网络节点属性 + * 构造函数,初始化部件并设置网络节点属性。 * @param partItem 部件物品 */ public EntitySpeedTickerPart(IPartItem partItem) { super(partItem); - // 设置网络节点属性:需要通道、空闲功耗为1,并注册为 IGridTickable 服务 this.getMainNode() .setFlags(GridFlags.REQUIRE_CHANNEL) .setIdlePowerUsage(1) .addService(IGridTickable.class, this); } - // 当前打开的菜单实例(如果有) - public EntitySpeedTickerMenu menu; - // 控制是否启用加速(默认启用) - private boolean accelerateEnabled = true; - // 标记网络中能量是否充足(用于 GUI 提示,默认充足) - private boolean networkEnergySufficient = true; public boolean getAccelerateEnabled() { return this.accelerateEnabled; @@ -90,29 +90,33 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka return this.networkEnergySufficient; } - public void setAccelerateEnabled(boolean accelerateEnabled) { - this.accelerateEnabled = accelerateEnabled; - } - /** - * 更新网络能量充足标记并在菜单存在且状态变化时触发同步 - * @param sufficient 是否能量充足 + * 设置加速开关状态并通知菜单。 + * @param enabled 是否启用加速 */ - private void updateNetworkEnergySufficient(boolean sufficient) { - // 保持部件内部状态一致(部件为权威来源) - this.networkEnergySufficient = sufficient; - if (this.menu != null) { - try { - // 使用菜单的封装方法更新并广播,以保持封装性 - this.menu.setNetworkEnergySufficient(sufficient); - } catch (Exception ignored) {} + public void setAccelerateEnabled(boolean enabled) { + this.accelerateEnabled = enabled; + if (menu != null) { + menu.setAccelerateEnabled(enabled); } } /** - * 获取当前状态下的静态模型(用于渲染) + * 更新网络能量充足状态并通知菜单。 + * @param sufficient 是否能量充足 + */ + private void updateNetworkEnergySufficient(boolean sufficient) { + this.networkEnergySufficient = sufficient; + if (menu != null) { + menu.setNetworkEnergySufficient(sufficient); + } + } + + /** + * 获取当前状态的渲染模型。 * @return 当前状态的模型 */ + @Override public IPartModel getStaticModels() { if (this.isActive() && this.isPowered()) { return MODELS_HAS_CHANNEL; @@ -196,87 +200,149 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka } /** - * 以指定速度对目标方块实体进行 tick 操作 - * - * @param blockEntity 需要被 tick 的方块实体 - * @param 方块实体类型 + * 对目标方块实体执行加速 tick 操作。 + * @param blockEntity 目标方块实体 + * @param 方块实体类型 */ private void ticker(@NotNull T blockEntity) { - if (this.getGridNode() == null - || this.getMainNode() == null - || this.getMainNode().getGrid() == null) { + if (!isValidForTicking()) { return; } - // 获取方块实体的位置 - BlockPos pos = blockEntity.getBlockPos(); - if (blockEntity.getLevel() == null) return; - - - // 检查黑名单(支持通配符/正则) String blockId = BuiltInRegistries.BLOCK.getKey(blockEntity.getBlockState().getBlock()).toString(); - - // 使用工具类的缓存接口(工具类内部负责懒加载/线程安全) - List compiledBlacklist = ConfigParsingUtils.getCachedBlacklist(ModConfigs.ENTITY_TICKER_BLACK_LIST.get()); - for (Pattern p : compiledBlacklist) { - if (p.matcher(blockId).matches()) return; + if (ConfigParsingUtils.isBlockBlacklisted(blockId, ModConfigs.ENTITY_TICKER_BLACK_LIST.get())) { + return; } - // 获取该方块实体的 Ticker - @SuppressWarnings("unchecked") - BlockEntityTicker blockEntityTicker = this.getLevel() - .getBlockState(pos) - .getTicker(this.getLevel(), (BlockEntityType) blockEntity.getType()); - if (blockEntityTicker == null) return; + BlockEntityTicker ticker = getTicker(blockEntity); + if (ticker == null) { + return; + } - // 使用集中定义的 CardDef 列表,支持以后添加等级或改倍率而无需修改此逻辑 - int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); + int speed = calculateSpeed(); + if (speed <= 0) { + return; + } - // 使用已注册的单一 Item 计算已安装卡数量(总计,用于能耗计算) + double requiredPower = calculateRequiredPower(speed, blockId); + if (!extractPower(requiredPower)) { + return; + } + + performTicks(blockEntity, ticker, speed); + } + + /** + * 检查网络节点是否有效。 + * @return 是否可以执行 tick + */ + private boolean isValidForTicking() { + return getGridNode() != null && getMainNode() != null && getMainNode().getGrid() != null; + } + + /** + * 获取目标方块实体的 ticker。 + * @param blockEntity 目标方块实体 + * @return ticker 或 null + */ + private BlockEntityTicker getTicker(T blockEntity) { + return getLevel().getBlockState(blockEntity.getBlockPos()) + .getTicker(getLevel(), (BlockEntityType) blockEntity.getType()); + } + + /** + * 计算加速倍率。 + * @return 生效的加速倍率 + */ + private int calculateSpeed() { int entitySpeedCardCount = getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get()); + if (entitySpeedCardCount <= 0) return 0; + return (int) PowerUtils.computeProductWithCap(getUpgrades(), 8); + } - // 使用工具方法从槽位直接计算乘积并应用 cap(最多 8 张卡) - long product = PowerUtils.computeProductWithCapFromStacks(this.getUpgrades(), 8); + /** + * 计算所需能量。 + * @param speed 加速倍率 + * @param blockId 目标方块ID + * @return 所需能量 + */ + private double calculateRequiredPower(int speed, String blockId) { + int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); + double multiplier = ConfigParsingUtils.getMultiplierForBlock(blockId, ModConfigs.ENTITY_TICKER_MULTIPLIERS.get()); + return PowerUtils.computeFinalPowerForProduct(speed, energyCardCount) * multiplier; + } - // 如果没有任何实体加速卡,则不进行加速且不消耗额外能量(只保留部件的被动功耗) - if (entitySpeedCardCount <= 0) return; + /** + * 提取网络能量并更新状态,优先从 AE2 网络提取 AE 能量,不足时从磁盘提取 FE 能量。 + * @param requiredPower 所需能量(AE 单位) + * @return 是否成功提取足够能量 + */ + private boolean extractPower(double requiredPower) { + IEnergyService energyService = getMainNode().getGrid().getEnergyService(); + MEStorage storage = getMainNode().getGrid().getStorageService().getInventory(); + IActionSource source = IActionSource.ofMachine(this); + boolean appFluxLoaded = ModList.get().isLoaded("appflux"); + boolean preferDiskEnergy = appFluxLoaded && ModConfigs.PRIORITIZE_DISK_ENERGY.get(); - // 计算本次 tick 所需能量:使用工具类根据 product 计算最终能耗 - double requiredPower = PowerUtils.computeFinalPowerForProduct(product, energyCardCount); - - int speed = (int) product; - - double multiplier = 1.0; - for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(ModConfigs.ENTITY_TICKER_MULTIPLIERS.get())) { - if (me.pattern.matcher(blockId).matches()) { - multiplier = Math.max(multiplier, me.multiplier); + // 如果 appflux 存在且优先磁盘能量,尝试提取 FE 能量 + if (appFluxLoaded && preferDiskEnergy) { + if (tryExtractFE(energyService, storage, requiredPower, source)) { + return true; } } - requiredPower *= multiplier; - -// 先模拟提取以检查网络中是否有足够能量,再真正抽取 - double simulated = getMainNode().getGrid().getEnergyService() - .extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG); - if (simulated < requiredPower) { - updateNetworkEnergySufficient(false); // 能量不足 - return; + // 尝试提取 AE 能量(当 appflux 不存在、优先 AE 能量或 FE 提取失败时) + double simulated = energyService.extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG); + if (simulated >= requiredPower) { + double extracted = energyService.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG); + boolean sufficient = extracted >= requiredPower; + updateNetworkEnergySufficient(sufficient); + return sufficient; } + updateNetworkEnergySufficient(false); - double extractedPower = getMainNode().getGrid().getEnergyService() - .extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG); - if (extractedPower < requiredPower) { - updateNetworkEnergySufficient(false); // 能量不足 - return; + // 如果 appflux 存在且优先 AE 能量,尝试提取 FE 能量作为备用 + if (appFluxLoaded && !preferDiskEnergy) { + return tryExtractFE(energyService, storage, requiredPower, source); } - updateNetworkEnergySufficient(true); // 能量充足 + return false; + } - // 计算加速倍数:基于 2 的次方,并把 8 张映射到最大 1024x(2^10) - // 已由 product 计算得到 speed;上面已在没有卡时提前返回 + private boolean tryExtractFE(IEnergyService energyService, MEStorage storage, double requiredPower, IActionSource source) { + try { + Class helperClass = Class.forName("com.extendedae_plus.util.FluxEnergyHelper"); + Method extractMethod = helperClass.getMethod( + "extractFE", + IEnergyService.class, + MEStorage.class, + long.class, + IActionSource.class + ); + long feRequired = (long) requiredPower << 1; // 1 AE = 2 FE + long feExtracted = (long) extractMethod.invoke(null, energyService, storage, feRequired, source); + if (feExtracted >= feRequired) { + updateNetworkEnergySufficient(true); + return true; + } + } catch (Exception e) { + // 如果反射失败,视为 FE 不可用 + } + updateNetworkEnergySufficient(false); + return false; + } - // 执行 tick 操作 + /** + * 执行加速 tick 操作。 + * @param blockEntity 目标方块实体 + * @param ticker 方块实体 ticker + * @param speed 加速倍率 + */ + private void performTicks(T blockEntity, + BlockEntityTicker ticker, + int speed) { + // 执行 speed-1 次额外 tick(原生 tick 已包含 1 次) for (int i = 0; i < speed - 1; i++) { - blockEntityTicker.tick( + ticker.tick( blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), diff --git a/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java b/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java index 30605bb..b9a4352 100644 --- a/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java +++ b/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java @@ -10,28 +10,32 @@ import appeng.client.gui.widgets.SettingToggleButton; import appeng.util.Platform; import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu; import com.extendedae_plus.network.ToggleEntityTickerC2SPacket; -import com.extendedae_plus.util.PowerUtils; +import com.extendedae_plus.util.entitySpeed.PowerUtils; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class EntitySpeedTickerScreen extends UpgradeableScreen { - private boolean eap$entitySpeedTickerEnabled = false; - private SettingToggleButton eap$entitySpeedTickerToggle; + private boolean eap$entitySpeedTickerEnabled = false; // 本地缓存的加速开关状态 + private final SettingToggleButton eap$entitySpeedTickerToggle; // 加速开关按钮 - public EntitySpeedTickerScreen( - EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { + /** + * 构造函数,初始化界面和控件。 + * @param menu 实体加速器菜单 + * @param playerInventory 玩家背包 + * @param title 界面标题 + * @param style 界面样式 + */ + public EntitySpeedTickerScreen(EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super((C) menu, playerInventory, title, style); - this.addToLeftToolbar(CommonButtons.togglePowerUnit()); + this.addToLeftToolbar(CommonButtons.togglePowerUnit()); // 添加功率单位切换按钮 + this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled(); - try{ - this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled(); - }catch (Exception ignored){} - - // 使用 SettingToggleButton 的外观(原版图标),但自定义悬停描述为“智能阻挡” - // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传 + // 初始化加速开关按钮 eap$entitySpeedTickerToggle = new SettingToggleButton<>( Settings.BLOCKING_MODE, this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO, @@ -43,65 +47,38 @@ public class EntitySpeedTickerScreen extends Up ) { @Override public List getTooltipMessage() { - // 如果目标在黑名单中,直接显示已禁用的提示 - try { - if (menu != null && menu.targetBlacklisted) { - var title = Component.literal("实体加速"); - var stateLine = Component.literal("已禁用(目标在黑名单)"); - return List.of(title, stateLine); - } - } catch (Exception ignored) {} - + if (menu.targetBlacklisted) { + return List.of( + Component.literal("实体加速"), + Component.literal("已禁用(目标在黑名单)") + ); + } boolean enabled = eap$entitySpeedTickerEnabled; - var title = Component.literal("实体加速"); - var stateLine = enabled - ? Component.literal("已启用: 将加速目标方块实体的tick") - : Component.literal("已关闭: 不会对目标方块实体进行加速"); - return List.of(title, stateLine); + return List.of( + Component.literal("实体加速"), + enabled ? Component.literal("已启用: 将加速目标方块实体的tick") : + Component.literal("已关闭: 不会对目标方块实体进行加速") + ); } @Override protected Icon getIcon() { - try { - if (menu != null && menu.targetBlacklisted) { - // 黑名单时显示禁用图标 - return Icon.INVALID; - } - } catch (Exception ignored) {} - - // 根据当前值显示不同图标(可按需替换 Icon 常量) - if (this.getCurrentValue() == YesNo.YES) { - return Icon.VALID; - } else { - return Icon.INVALID; - } + if (menu.targetBlacklisted) return Icon.INVALID; + return this.getCurrentValue() == YesNo.YES ? Icon.VALID : Icon.INVALID; } }; - // 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致 eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO); - this.addToLeftToolbar(eap$entitySpeedTickerToggle); } @Override protected void updateBeforeRender() { super.updateBeforeRender(); - - if (this.eap$entitySpeedTickerToggle != null) { - boolean desired = this.eap$entitySpeedTickerEnabled; - if (this.menu != null) { - desired = this.menu.getAccelerateEnabled(); - } - - this.eap$entitySpeedTickerEnabled = desired; - // 如果目标在黑名单中,禁用切换并强制显示为关闭 - if (this.menu != null && this.menu.targetBlacklisted) { - this.eap$entitySpeedTickerToggle.set(YesNo.NO); - this.eap$entitySpeedTickerToggle.active = false; - } else { - this.eap$entitySpeedTickerToggle.set(desired ? YesNo.YES : YesNo.NO); - this.eap$entitySpeedTickerToggle.active = true; - } + if (eap$entitySpeedTickerToggle != null && menu != null) { + eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled(); + // 如果目标在黑名单,禁用按钮并显示关闭状态 + eap$entitySpeedTickerToggle.set(menu.targetBlacklisted ? YesNo.NO : (eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO)); + eap$entitySpeedTickerToggle.active = !menu.targetBlacklisted; } textData(); } @@ -110,34 +87,33 @@ public class EntitySpeedTickerScreen extends Up textData(); } + /** + * 更新界面文本内容,包括加速状态、速度、能耗和倍率。 + */ private void textData() { - // 如果目标被黑名单禁止,则显示禁用状态并把数值显示为 0 + Map textContents = new HashMap<>(); if (getMenu().targetBlacklisted) { - setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable")); - setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0)); - setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false))); - setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0))); - setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0))); - return; + // 黑名单禁用时的默认显示 + textContents.put("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable")); + textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0)); + textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false))); + textContents.put("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0))); + textContents.put("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0))); + } else { + // 正常状态下显示实际数据 + int energyCardCount = getMenu().energyCardCount; + double multiplier = getMenu().multiplier; + int effectiveSpeed = getMenu().effectiveSpeed; + double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount); + double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount); + + textContents.put("enable", getMenu().networkEnergySufficient ? null : + Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient")); + textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed)); + textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false))); + textContents.put("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio))); + textContents.put("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier))); } - - int energyCardCount = getMenu().energyCardCount; - double multiplier = getMenu().multiplier; - int effectiveSpeed = getMenu().effectiveSpeed; - - double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount); - double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount); - - // 如果网络能量不足,优先显示警告信息并在能量值处显示 0 - if (!getMenu().networkEnergySufficient) { - setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient")); - }else { - setTextContent("enable", null); - } - - setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed)); - setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false))); - setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio))); - setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier))); + textContents.forEach(this::setTextContent); } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/config/ModConfigs.java b/src/main/java/com/extendedae_plus/config/ModConfigs.java index bd6665e..9681b90 100644 --- a/src/main/java/com/extendedae_plus/config/ModConfigs.java +++ b/src/main/java/com/extendedae_plus/config/ModConfigs.java @@ -13,6 +13,7 @@ public final class ModConfigs { public static final ModConfigSpec CLIENT_SPEC; public static final ModConfigSpec.BooleanValue SHOW_ENCODER_PATTERN_PLAYER; public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT; + public static final ModConfigSpec.BooleanValue PRIORITIZE_DISK_ENERGY; // Server 配置 public static final ModConfigSpec SERVER_SPEC; @@ -124,6 +125,12 @@ public final class ModConfigs { () -> "", obj -> obj instanceof String ); + PRIORITIZE_DISK_ENERGY = serverBuilder + .comment( + "是否优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)", + "开启后,将优先尝试从磁盘提取FE能量;反之优先消耗AE网络中的能量" + ) + .define("prioritizeDiskEnergy", true); serverBuilder.pop(); SERVER_SPEC = serverBuilder.build(); } diff --git a/src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java b/src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java new file mode 100644 index 0000000..6e5a57a --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java @@ -0,0 +1,41 @@ +package com.extendedae_plus.util; + +import appeng.api.config.Actionable; +import appeng.api.networking.energy.IEnergyService; +import appeng.api.networking.security.IActionSource; +import appeng.api.storage.MEStorage; +import appeng.api.storage.StorageHelper; +import com.glodblock.github.appflux.common.me.key.FluxKey; +import com.glodblock.github.appflux.common.me.key.type.EnergyType; + +public class FluxEnergyHelper { + /** + * 尝试从 ME 存储提取 FE 能量。 + * @param energyService AE2 能量服务 + * @param storage ME 存储 + * @param feRequired 所需 FE 量 + * @param source 操作来源 + * @return 提取的 FE 量 + */ + public static long extractFE( + IEnergyService energyService, + MEStorage storage, + long feRequired, + IActionSource source + ) { + FluxKey feKey = FluxKey.of(EnergyType.FE); + + // 模拟提取 FE + long feExtracted = StorageHelper.poweredExtraction( + energyService, storage, feKey, feRequired, source, Actionable.SIMULATE + ); + + // 执行实际提取 + if (feExtracted >= feRequired) { + return StorageHelper.poweredExtraction( + energyService, storage, feKey, feRequired, source, Actionable.MODULATE + ); + } + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/PowerUtils.java b/src/main/java/com/extendedae_plus/util/PowerUtils.java deleted file mode 100644 index 4da3f2c..0000000 --- a/src/main/java/com/extendedae_plus/util/PowerUtils.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.extendedae_plus.util; - -import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem; -import com.extendedae_plus.config.ModConfigs; - - -/** - * 用于计算实体加速器的能耗与加速倍率的工具类 - */ -public final class PowerUtils { - private PowerUtils() {} - // ---- 重构后的 API ---- - /** - * 将 card multipliers(按插槽序)计算乘积并应用 cap 规则(见 capForHighestMultiplier) - * @param multipliers iterable of per-card multipliers - * @param maxCards 最多计入的卡数 - * @return 被 cap 约束后的乘积 - */ - public static long computeProductWithCap(Iterable multipliers, int maxCards) { - long product = 1L; - int considered = 0; - int highest = 1; - for (Integer m : multipliers) { - if (m == null) continue; - if (considered >= maxCards) break; - int mult = m.intValue(); - if (mult <= 0) mult = 1; - product *= mult; - highest = Math.max(highest, mult); - considered++; - } - long cap = capForHighestMultiplier(highest); - return Math.min(product, cap); - } - - /** - * 根据最高单卡 multiplier 返回 cap 值 - */ - public static long capForHighestMultiplier(int highestMultiplier) { - if (highestMultiplier >= 16) return 1024L; - if (highestMultiplier >= 8) return 256L; - if (highestMultiplier >= 4) return 64L; - if (highestMultiplier >= 2) return 8L; - return 1L; - } - - /** - * 从菜单对象读取前 maxCards 个加速卡的 multiplier 并计算 product with cap - */ - public static long computeProductWithCapFromMenu(appeng.menu.implementations.UpgradeableMenu menu, int maxCards) { - java.util.List list = new java.util.ArrayList<>(); - int considered = 0; - for (var stack : menu.getUpgrades()) { - if (considered >= maxCards) break; - if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) { - int multVal = EntitySpeedCardItem.readMultiplier(stack); - int count = Math.min(stack.getCount(), maxCards - considered); - for (int i = 0; i < count; i++) { - list.add(multVal); - considered++; - if (considered >= maxCards) break; - } - } - } - return computeProductWithCap(list, maxCards); - } - - /** - * 从一组 ItemStack(升级槽)直接计算 product with cap(最多 maxCards) - */ - public static long computeProductWithCapFromStacks(Iterable stacks, int maxCards) { - java.util.List list = new java.util.ArrayList<>(); - int considered = 0; - for (var stack : stacks) { - if (considered >= maxCards) break; - if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) { - int multVal = EntitySpeedCardItem.readMultiplier(stack); - int count = Math.min(stack.getCount(), maxCards - considered); - for (int i = 0; i < count; i++) { - list.add(multVal); - considered++; - if (considered >= maxCards) break; - } - } - } - return computeProductWithCap(list, maxCards); - } - - /** - * 计算最终消耗:把 product 转换为等效卡数(log2)并调用 getFinalPower - */ - public static double computeFinalPowerForProduct(long product, int energyCardCount) { - if (product <= 1L) return 0.0; - double base = ModConfigs.ENTITY_TICKER_COST.getAsInt(); - - // 计算以2为底的对数(用于分档与公式) - double log2 = Math.log(product) / Math.log(2.0); - - // 分档:product==2 为一档;4..256 为中档;512..1024 为高档 - double raw; - if (product == 2L) { - // 轻量档:线性小幅增长 - raw = base * 4; - } else if (product <= 256L) { - // 中档:增长放缓(使用 1.5 * log2) - raw = base * Math.pow(2.0, 1.5 * log2) * 2; - } else { - // 高档:增长较快(使用 2.5 * log2) - raw = base * Math.pow(2.0, 2.5 * log2); - } - - double reduction = getReductionPercent(energyCardCount); - return raw * (1.0 - reduction) / 8.0; - } - - /* ----------------- legacy helpers (restored) ----------------- */ - public static double getGrowthFactor(int speedCardCount) { - if (speedCardCount <= 0) return 1.0; - if (speedCardCount == 1) return 2.0; - if (speedCardCount <= 6) return Math.pow(2.0, 2.0 * speedCardCount); - return Math.pow(2.0, 3.0 * speedCardCount); - } - - public static double getRawPower(int speedCardCount) { - double base = ModConfigs.ENTITY_TICKER_COST.getAsInt(); - return base * getGrowthFactor(speedCardCount); - } - - public static double getReductionPercent(int energyCardCount) { - if (energyCardCount <= 0) return 0.0; - if (energyCardCount == 1) return 0.1; - if (energyCardCount >= 8) return 0.5; - return 0.5 * (1.0 - Math.pow(0.7, energyCardCount)); - } - - public static double getFinalPower(int speedCardCount, int energyCardCount) { - double raw = getRawPower(speedCardCount); - double reduction = getReductionPercent(energyCardCount); - return raw * (1.0 - reduction) / 4.0; - } - - /** - * 返回能源卡减免后剩余的功耗比率(例如 1 张能源卡 -> 0.9) - * @param energyCardCount 能源卡数量 - * @return 剩余功耗比率 - */ - public static double getRemainingRatio(int energyCardCount) { - return 1.0 - getReductionPercent(energyCardCount); - } - - /** - * 将剩余功耗比率格式化为百分比字符串(例如 0.9 -> "90%") - */ - public static String formatPercentage(double ratio) { - double pct = ratio * 100.0; - // 如果为整数则无小数 - if (Math.abs(pct - Math.round(pct)) < 1e-9) { - return String.format("%d%%", Math.round(pct)); - } - return String.format("%.2f%%", pct); - } -} diff --git a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java b/src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java similarity index 68% rename from src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java rename to src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java index 0cf6ddc..6fd3b68 100644 --- a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java +++ b/src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java @@ -1,10 +1,13 @@ -package com.extendedae_plus.util; +package com.extendedae_plus.util.entitySpeed; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; + +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + /** * 配置解析工具类:用于解析黑名单与倍率配置的字符串 */ @@ -23,35 +26,32 @@ public final class ConfigParsingUtils { /** * 编译用户提供的匹配串。支持简单的 glob 语法('*' 和 '?')以及完整的正则表达式。 - * 规则: - * - 如果字符串包含 ".*" 或其他正则元字符,优先尝试按正则编译(若失败则回退到 glob 转换)。 - * - 否则若包含 '*' 或 '?',将按 glob 语法转换为正则。 - * - 否则按字面量匹配处理。 */ public static Pattern compilePattern(String raw) { - if (raw == null) throw new IllegalArgumentException("pattern is null"); + if (raw == null || raw.trim().isEmpty()) { + LOGGER.warn("Invalid pattern: {}", raw); + throw new IllegalArgumentException("Pattern is null or empty"); + } raw = raw.trim(); - if (raw.isEmpty()) throw new IllegalArgumentException("pattern is empty"); - // If it looks like regex (contains '.*' or regex metachar), try regex first + // Try regex first if it contains regex metacharacters if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) { try { return Pattern.compile("^" + raw + "$"); - } catch (PatternSyntaxException ignored) { - // fallback to glob below + } catch (PatternSyntaxException e) { + LOGGER.warn("Failed to compile regex pattern '{}': {}", raw, e.getMessage()); + // Fallback to glob } } - // If contains glob chars, convert to regex + // Convert glob to regex if (raw.contains("*") || raw.contains("?")) { - StringBuilder sb = new StringBuilder(); - sb.append('^'); + StringBuilder sb = new StringBuilder("^"); for (char c : raw.toCharArray()) { switch (c) { case '*': sb.append(".*"); break; case '?': sb.append('.'); break; default: - // escape regex special chars if (".\\+[]{}()^$|".indexOf(c) >= 0) { sb.append('\\'); } @@ -62,41 +62,71 @@ public final class ConfigParsingUtils { return Pattern.compile(sb.toString()); } - // Otherwise treat as a literal (match exact) + // Literal match return Pattern.compile("^" + Pattern.quote(raw) + "$"); } /** - * Parse multiplier entries like 'modid:block 2x' into MultiplierEntry objects. - * Accepts values with optional trailing 'x' (case-insensitive). + * 解析倍率条目,如 'modid:block 2x'。 */ public static MultiplierEntry parseMultiplierEntry(String entry) { - if (entry == null) return null; + if (entry == null || entry.trim().isEmpty()) return null; String[] parts = entry.trim().split("\\s+"); - if (parts.length < 2) return null; + if (parts.length < 2) { + LOGGER.warn("Invalid multiplier entry: {}", entry); + return null; + } String key = parts[0]; String val = parts[1].toLowerCase(); if (val.endsWith("x")) val = val.substring(0, val.length() - 1); - double m; + double multiplier; try { - m = Double.parseDouble(val); - } catch (NumberFormatException ex) { + multiplier = Double.parseDouble(val); + } catch (NumberFormatException e) { + LOGGER.warn("Invalid multiplier value in '{}': {}", entry, val); return null; } try { - Pattern p = compilePattern(key); - return new MultiplierEntry(p, m); + Pattern pattern = compilePattern(key); + return new MultiplierEntry(pattern, multiplier); } catch (IllegalArgumentException e) { + LOGGER.warn("Failed to compile pattern in '{}': {}", entry, e.getMessage()); return null; } } + /** + * 检查方块是否在黑名单中。 + */ + public static boolean isBlockBlacklisted(String blockId, List blacklist) { + if (blockId == null) return false; + return getCachedBlacklist(blacklist).stream().anyMatch(p -> p.matcher(blockId).matches()); + } + + /** + * 获取方块的倍率。 + */ + public static double getMultiplierForBlock(String blockId, List multipliers) { + if (blockId == null) return 1.0; + double maxMultiplier = 1.0; + for (MultiplierEntry me : getCachedMultiplierEntries(multipliers)) { + if (me.pattern.matcher(blockId).matches()) { + maxMultiplier = Math.max(maxMultiplier, me.multiplier); + } + } + return maxMultiplier; + } + public static List compilePatterns(List raw) { List out = new ArrayList<>(); if (raw == null) return out; for (String s : raw) { - if (s == null || s.isBlank()) continue; - try { out.add(compilePattern(s)); } catch (IllegalArgumentException ignored) {} + if (s == null || s.trim().isEmpty()) continue; + try { + out.add(compilePattern(s)); + } catch (IllegalArgumentException e) { + LOGGER.warn("Failed to compile pattern '{}': {}", s, e.getMessage()); + } } return out; } @@ -121,14 +151,11 @@ public final class ConfigParsingUtils { /** * 获取已解析并缓存的黑名单(线程安全、懒加载)。 */ - public static List getCachedBlacklist(java.util.List source) { + public static List getCachedBlacklist(List source) { List normalized = normalizeSource(source); - - // fast path: identical snapshot reference or equal contents if (cachedBlacklist != null && listEquals(cachedBlacklistSourceSnapshot, normalized)) { return Collections.unmodifiableList(cachedBlacklist); } - synchronized (CACHE_LOCK) { if (cachedBlacklist == null || !listEquals(cachedBlacklistSourceSnapshot, normalized)) { cachedBlacklist = compilePatterns(normalized); @@ -141,13 +168,11 @@ public final class ConfigParsingUtils { /** * 获取已解析并缓存的倍率列表(线程安全、懒加载)。 */ - public static List getCachedMultiplierEntries(java.util.List source) { + public static List getCachedMultiplierEntries(List source) { List normalized = normalizeSource(source); - if (cachedMultiplierEntries != null && listEquals(cachedMultiplierSourceSnapshot, normalized)) { return Collections.unmodifiableList(cachedMultiplierEntries); } - synchronized (CACHE_LOCK) { if (cachedMultiplierEntries == null || !listEquals(cachedMultiplierSourceSnapshot, normalized)) { cachedMultiplierEntries = parseMultiplierList(normalized); @@ -158,7 +183,7 @@ public final class ConfigParsingUtils { } // Normalize the incoming source list: trim entries, drop blanks, keep stable ordering - private static List normalizeSource(java.util.List source) { + private static List normalizeSource(List source) { List out = new ArrayList<>(); if (source == null) return out; for (String s : source) { @@ -188,8 +213,8 @@ public final class ConfigParsingUtils { synchronized (CACHE_LOCK) { cachedBlacklist = null; cachedMultiplierEntries = null; + cachedBlacklistSourceSnapshot = null; + cachedMultiplierSourceSnapshot = null; } } -} - - +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/entitySpeed/PowerUtils.java b/src/main/java/com/extendedae_plus/util/entitySpeed/PowerUtils.java new file mode 100644 index 0000000..f095dc6 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/entitySpeed/PowerUtils.java @@ -0,0 +1,98 @@ +package com.extendedae_plus.util.entitySpeed; + +import appeng.api.upgrades.IUpgradeInventory; +import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem; +import com.extendedae_plus.config.ModConfigs; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用于计算实体加速器的能耗与加速倍率的工具类 + */ +public final class PowerUtils { + private PowerUtils() {} + + /** + * 计算加速卡的乘积并应用上限。 + * @param upgrades 升级槽位 + * @param maxCards 最大计入的卡数 + * @return 被上限约束后的乘积 + */ + public static long computeProductWithCap(IUpgradeInventory upgrades, int maxCards) { + List multipliers = new ArrayList<>(); + int considered = 0; + for (ItemStack stack : upgrades) { + if (considered >= maxCards) break; + if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) { + int multVal = EntitySpeedCardItem.readMultiplier(stack); + int count = Math.min(stack.getCount(), maxCards - considered); + for (int i = 0; i < count; i++) { + multipliers.add(multVal); + considered++; + } + } + } + long product = 1L; + int highest = 1; + for (Integer m : multipliers) { + if (m == null || m <= 0) continue; + product *= m; + highest = Math.max(highest, m); + } + return Math.min(product, capForHighestMultiplier(highest)); + } + + /** + * 根据最高单卡倍率返回上限值。 + */ + public static long capForHighestMultiplier(int highestMultiplier) { + if (highestMultiplier >= 16) return 1024L; + if (highestMultiplier >= 8) return 256L; + if (highestMultiplier >= 4) return 64L; + if (highestMultiplier >= 2) return 8L; + return 1L; + } + + /** + * 计算最终能耗。 + * @param product 加速卡乘积 + * @param energyCardCount 能量卡数量 + * @return 最终能耗值 + */ + public static double computeFinalPowerForProduct(long product, int energyCardCount) { + if (product <= 1L) return 0.0; + double base = ModConfigs.ENTITY_TICKER_COST.get(); + double log2 = Math.log(product) / Math.log(2.0); + double raw; + if (product == 2L) { + raw = base * 4; + } else if (product <= 256L) { + raw = base * Math.pow(2.0, 1.5 * log2) * 2; + } else { + raw = base * Math.pow(2.0, 2.5 * log2); + } + return raw * getRemainingRatio(energyCardCount) / 8.0; + } + + /** + * 计算能量卡减免后的剩余功耗比率。 + */ + public static double getRemainingRatio(int energyCardCount) { + if (energyCardCount <= 0) return 1.0; + if (energyCardCount == 1) return 0.9; + if (energyCardCount >= 8) return 0.5; + return 1.0 - 0.5 * (1.0 - Math.pow(0.7, energyCardCount)); + } + + /** + * 将剩余功耗比率格式化为百分比字符串。 + */ + public static String formatPercentage(double ratio) { + double pct = ratio * 100.0; + return Math.abs(pct - Math.round(pct)) < 1e-9 ? + String.format("%d%%", Math.round(pct)) : + String.format("%.2f%%", pct); + } +} \ No newline at end of file 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 c8d3754..43a24bf 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -94,6 +94,7 @@ "extendedae_plus.configuration.entityTickerCost": "Entity Ticker Base Energy Cost", "extendedae_plus.configuration.entityTickerBlackList": "Entity Ticker Blacklist", "extendedae_plus.configuration.entityTickerMultipliers": "Entity Ticker Extra Cost Multipliers", + "extendedae_plus.configuration.prioritizeDiskEnergy": "Prioritize FE energy from disk (requires Applied Flux)", "extendedae_plus.configuration.state_on": "On", "extendedae_plus.configuration.state_off": "Off" diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 3df2054..195ba86 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -94,6 +94,7 @@ "extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值", "extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单", "extendedae_plus.configuration.entityTickerMultipliers": "实体加速器额外消耗倍率", + "extendedae_plus.configuration.prioritizeDiskEnergy": "优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)", "extendedae_plus.configuration.state_on": "开", "extendedae_plus.configuration.state_off": "关"