diff --git a/.gitignore b/.gitignore index 37b19ff..16b0b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ build/ out/ classes/ source/ -Applied-Energistics-2-forge-1.20.1/ + # Eclipse *.tmp *.bak diff --git a/CHANGELOG.md b/CHANGELOG.md index 28f0d04..0094f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +## [1.4.4] +### Added / 新增 +- 实体加速器支持对 AppliedFlux 存储电量的扣除 + - Entity Accelerator now supports deducting energy from AppliedFlux storage. +- 为无线收发器添加已使用频道数的 Jade 显示 + - Added Jade HUD display showing used channel count for Wireless Transceivers. +- 无线收发器支持绑定 FTB Teams,不同队伍将不再共用频率 + - Wireless Transceivers now support FTB Teams binding; different teams no longer share the same frequency. +- 频道卡新增潜行左键操作:绑定 team/uuid(必须绑定,否则无法连接无线收发器) + - Channel Card: new Shift+LMB operation to bind team/uuid (binding required to connect to Wireless Transceivers). +- 添加使用扳手潜行左键无线收发器,可以快捷地调整频率 + - Added Shift+LMB with wrench on Wireless Transceivers to quickly adjust frequency. + +### Changed / 变更 +- 优化实体加速器性能与逻辑 + - Optimized Entity Accelerator performance and logic. + +### Fixed / 修复 +- 修复游戏未添加 JEI 时的崩溃问题 + - Fixed crash when JEI mod is not present. +- 修复选择上传样板供应器时,显示的名称为客户端语言 + - Fixed pattern provider names being displayed in client language when selecting upload targets. + ## [1.4.3] ### ⚠️ 重要提醒 / Important Notice - **更新会导致旧版的无限盘失效,如有需要请先导盘再更新** diff --git a/build.gradle b/build.gradle index d047627..00e8bf2 100644 --- a/build.gradle +++ b/build.gradle @@ -72,6 +72,7 @@ dependencies { //extendedAE modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar') modRuntimeOnly fileTree(dir: 'libs', includes: ['*.jar']) + //ae2 modImplementation "appeng:appliedenergistics2-forge:${ae2_version}" modImplementation "org.appliedenergistics:guideme:${guideme_version}" @@ -121,12 +122,16 @@ dependencies { // compileOnly 供代码编译 include "curse.maven:configuration-444699:4710266" modImplementation "curse.maven:configuration-444699:4710266" + + //ftbteams + modCompileOnly "curse.maven:ftb-teams-forge-404468:6130786" + modCompileOnly "curse.maven:ftb-library-forge-404465:6807424" + modRuntimeOnly "curse.maven:ftb-teams-forge-404468:6130786" + modRuntimeOnly "curse.maven:ftb-library-forge-404465:6807424" } -allprojects { - tasks.withType(JavaCompile).configureEach { - options.compilerArgs << "-Xlint:-deprecation" - } +compileJava { + options.compilerArgs += ['-Xlint:-deprecation', '-Xlint:-removal', '-Xlint:-unchecked'] } processResources { diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 037a65a..011f78a 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -4,10 +4,10 @@ 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.command.InfinityDiskGiveCommand; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; import com.extendedae_plus.menu.locator.CuriosItemLocator; +import com.extendedae_plus.util.command.InfinityDiskGiveCommand; import com.extendedae_plus.util.storage.InfinityStorageManager; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; diff --git a/src/main/java/com/extendedae_plus/ae/api/config/Settings.java b/src/main/java/com/extendedae_plus/ae/api/config/Settings.java new file mode 100644 index 0000000..6ed3fc5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/config/Settings.java @@ -0,0 +1,41 @@ +package com.extendedae_plus.ae.api.config; + +import appeng.api.config.Setting; +import appeng.api.config.YesNo; +import com.google.common.base.Preconditions; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +public final class Settings { + private static final Map> SETTINGS = new HashMap<>(); + public static final Setting ACCELERATE = register("accelerate", YesNo.NO, YesNo.YES); + + + private Settings() { + } + + private synchronized static > Setting register(String name, Class enumClass) { + Preconditions.checkState(!SETTINGS.containsKey(name)); + var setting = new Setting<>(name, enumClass); + SETTINGS.put(name, setting); + return setting; + } + + @SafeVarargs + private synchronized static > Setting register(String name, T firstOption, T... moreOptions) { + Preconditions.checkState(!SETTINGS.containsKey(name)); + var setting = new Setting(name, firstOption.getDeclaringClass(), EnumSet.of(firstOption, moreOptions)); + SETTINGS.put(name, setting); + return setting; + } + + public static Setting getOrThrow(String name) { + var setting = SETTINGS.get(name); + if (setting == null) { + throw new IllegalArgumentException("Unknown setting '" + name + "'"); + } + return setting; + } +} diff --git a/src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java b/src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPattern.java similarity index 98% rename from src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java rename to src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPattern.java index 0b672ff..c402127 100644 --- a/src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java +++ b/src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPattern.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.content; +package com.extendedae_plus.ae.api.crafting; import appeng.api.crafting.IPatternDetails; import appeng.api.stacks.AEItemKey; @@ -15,7 +15,7 @@ import java.util.Objects; * 缩放后的处理样板,结构完全模拟 AEProcessingPattern。 * 保持 sparse/condensed/inputs 的一致性,同时保存原始样板。 */ -public final class ScaledProcessingPattern implements IPatternDetails { +public class ScaledProcessingPattern implements IPatternDetails { // 最小化实例字段:只保留原始样板引用、定义和倍数 private final AEProcessingPattern original; // 原始样板引用 diff --git a/src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPatternAdv.java b/src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPatternAdv.java new file mode 100644 index 0000000..f36a716 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/crafting/ScaledProcessingPatternAdv.java @@ -0,0 +1,69 @@ +package com.extendedae_plus.ae.api.crafting; + +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; +import appeng.crafting.pattern.AEProcessingPattern; +import net.minecraft.core.Direction; +import net.pedroksl.advanced_ae.common.patterns.AdvPatternDetails; + +import java.util.HashMap; + +/** + * Advanced AE 扩展版,额外实现 AdvPatternDetails 接口。 + * 仅在 Advanced AE 加载时使用。 + */ +public final class ScaledProcessingPatternAdv extends ScaledProcessingPattern implements AdvPatternDetails { + + private final AdvPatternDetails adv; + + public ScaledProcessingPatternAdv(AEProcessingPattern original, AEItemKey definition, long multiplier) { + super(original, definition, multiplier); + this.adv = (AdvPatternDetails) original; + } + + @Override + public boolean directionalInputsSet() { + return adv.directionalInputsSet(); + } + + @Override + public HashMap getDirectionMap() { + return adv.getDirectionMap(); + } + + @Override + public Direction getDirectionSideForInputKey(AEKey key) { + return adv.getDirectionSideForInputKey(key); + } + + @Override + public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) { + // 使用 lazy 计算的 sparseInputs 与 inputs 来驱动;当两者长度一致时直接委托 + GenericStack[] sInputs = getSparseInputs(); + IInput[] ins = getInputs(); + if (sInputs.length == ins.length) { + super.pushInputsToExternalInventory(inputHolder, inputSink); + return; + } + + KeyCounter allInputs = new KeyCounter(); + for (KeyCounter counter : inputHolder) { + allInputs.addAll(counter); + } + for (GenericStack sparseInput : sInputs) { + if (sparseInput != null) { + AEKey key = sparseInput.what(); + long amount = sparseInput.amount(); + long available = allInputs.get(key); + if (available < amount) { + throw new RuntimeException("Expected at least %d of %s when pushing scaled pattern, but only %d available" + .formatted(amount, key, available)); + } + inputSink.pushInput(key, amount); + allInputs.remove(key, amount); + } + } + } +} 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 efcce92..9c69845 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 @@ -326,8 +326,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (amount == 0){ return 0; } - // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { + // 不允许存储有物品的无限单元 + if (what instanceof AEItemKey itemKey && + itemKey.getItem() instanceof InfinityBigIntegerCellItem && + itemKey.hasTag() + ) { return 0; } diff --git a/src/main/java/com/extendedae_plus/NewIcon.java b/src/main/java/com/extendedae_plus/ae/client/gui/NewIcon.java similarity index 91% rename from src/main/java/com/extendedae_plus/NewIcon.java rename to src/main/java/com/extendedae_plus/ae/client/gui/NewIcon.java index 8535a1f..db82062 100644 --- a/src/main/java/com/extendedae_plus/NewIcon.java +++ b/src/main/java/com/extendedae_plus/ae/client/gui/NewIcon.java @@ -1,6 +1,7 @@ -package com.extendedae_plus; +package com.extendedae_plus.ae.client.gui; import appeng.client.gui.style.Blitter; +import com.extendedae_plus.ExtendedAEPlus; import net.minecraft.resources.ResourceLocation; public class NewIcon { diff --git a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java index 1d68551..ef186cd 100644 --- a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java @@ -1,8 +1,10 @@ package com.extendedae_plus.ae.items; import appeng.items.materials.UpgradeCardItem; +import com.extendedae_plus.util.WirelessTeamUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; @@ -13,13 +15,19 @@ import net.minecraft.world.level.Level; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.UUID; /** - * 频道卡(MVP):仅存储一个 long 类型的频道号到 NBT:"channel"。 + * 频道卡:存储频道号、所有者UUID和团队信息 + * - 右键空气:增加频道号 + * - 潜行左键空气:写入/清除玩家UUID和团队信息 + * - 潜行左键收发器:将频道卡的所有者信息写入收发器 * 继承 AE2 的 UpgradeCardItem 以复用升级卡判定与提示框架。 */ public class ChannelCardItem extends UpgradeCardItem { public static final String TAG_CHANNEL = "channel"; + public static final String TAG_OWNER_UUID = "ownerUUID"; + public static final String TAG_TEAM_NAME = "teamName"; // 用于显示 public ChannelCardItem(Item.Properties properties) { super(properties); @@ -35,29 +43,157 @@ public class ChannelCardItem extends UpgradeCardItem { return tag != null && tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L; } + /** + * 设置频道卡的所有者UUID + */ + public static void setOwnerUUID(ItemStack stack, UUID ownerUUID) { + CompoundTag tag = stack.getOrCreateTag(); + tag.putUUID(TAG_OWNER_UUID, ownerUUID); + } + + /** + * 获取频道卡的所有者UUID + */ + @Nullable + public static UUID getOwnerUUID(ItemStack stack) { + CompoundTag tag = stack.getTag(); + return tag != null && tag.hasUUID(TAG_OWNER_UUID) ? tag.getUUID(TAG_OWNER_UUID) : null; + } + + /** + * 设置团队名称(用于显示) + */ + public static void setTeamName(ItemStack stack, String teamName) { + CompoundTag tag = stack.getOrCreateTag(); + tag.putString(TAG_TEAM_NAME, teamName); + } + + /** + * 获取团队名称 + */ + @Nullable + public static String getTeamName(ItemStack stack) { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains(TAG_TEAM_NAME) ? tag.getString(TAG_TEAM_NAME) : null; + } + + /** + * 清除所有者信息 + */ + public static void clearOwner(ItemStack stack) { + CompoundTag tag = stack.getTag(); + if (tag != null) { + tag.remove(TAG_OWNER_UUID); + tag.remove(TAG_TEAM_NAME); + } + } + @Override public void appendHoverText(ItemStack stack, @Nullable Level level, List lines, TooltipFlag flag) { super.appendHoverText(stack, level, lines, flag); + + // 显示频道 long ch = getChannel(stack); if (ch == 0L) { lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset")); } else { lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch)); } + + // 显示所有者信息 + UUID ownerUUID = getOwnerUUID(stack); + String teamName = getTeamName(stack); + + if (ownerUUID != null) { + if (teamName != null && !teamName.isEmpty()) { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.team", teamName)); + } else { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.player", ownerUUID.toString().substring(0, 8))); + } + } else { + lines.add(Component.translatable("item.extendedae_plus.channel_card.owner.unset")); + } } @Override public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); + if (!level.isClientSide) { long ch = getChannel(stack); - boolean dec = player.isShiftKeyDown(); - long next = dec ? Math.max(0L, ch - 1L) : ch + 1L; + long next; + + if (player.isShiftKeyDown()) { + // 潜行右键:减少频率 + next = Math.max(0L, ch - 1L); + } else { + // 普通右键:增加频率 + next = ch + 1L; + } + if (next != ch) { setChannel(stack, next); player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true); } } + return InteractionResultHolder.sidedSuccess(stack, level.isClientSide); } + + @Override + public boolean onLeftClickEntity(ItemStack stack, Player player, net.minecraft.world.entity.Entity entity) { + // 左键实体时不做任何事,避免伤害实体 + if (player.isShiftKeyDown()) { + return true; // 取消默认行为 + } + return super.onLeftClickEntity(stack, player, entity); + } + + @Override + public boolean onBlockStartBreak(ItemStack stack, net.minecraft.core.BlockPos pos, Player player) { + // 潜行左键方块时触发绑定/解绑 + if (!player.isShiftKeyDown()) { + return false; // 不拦截 + } + + if (player.level().isClientSide) { + return true; // 客户端拦截,防止破坏方块 + } + + // 服务端处理 + if (player.level() instanceof ServerLevel serverLevel) { + // 检查是否是收发器(让Block类处理) + var blockState = player.level().getBlockState(pos); + if (blockState.getBlock() instanceof com.extendedae_plus.content.wireless.WirelessTransceiverBlock) { + return false; // 不拦截,让Block类处理 + } + + // 执行绑定/解绑 + UUID currentOwner = getOwnerUUID(stack); + + if (currentOwner != null) { + // 已有所有者,清除 + clearOwner(stack); + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.cleared"), + true + ); + } else { + // 写入当前玩家的UUID和团队信息 + UUID playerUUID = player.getUUID(); + setOwnerUUID(stack, playerUUID); + + // 获取团队名称用于显示 + Component teamName = WirelessTeamUtil.getNetworkOwnerName(serverLevel, playerUUID); + setTeamName(stack, teamName.getString()); + + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.bound", teamName), + true + ); + } + } + + return 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 2b4af58..3e5120d 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -17,8 +17,10 @@ import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; +import java.math.BigInteger; import java.util.List; import java.util.Objects; +import java.util.UUID; public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem { @@ -59,7 +61,7 @@ public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchIt if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) { try { byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL); - java.math.BigInteger total = new java.math.BigInteger(bytes); + BigInteger total = new BigInteger(bytes); String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); tooltip.add( Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) @@ -70,7 +72,7 @@ public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchIt } else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) { try { byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT); - java.math.BigInteger total = new java.math.BigInteger(bytes); + BigInteger total = new BigInteger(bytes); String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); tooltip.add( Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) @@ -85,7 +87,7 @@ public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchIt /** * 创建一个带有指定 UUID 的 Infinity 磁盘 ItemStack */ - public static ItemStack withUUID(java.util.UUID uuid) { + public static ItemStack withUUID(UUID uuid) { ItemStack stack = new ItemStack(Objects.requireNonNull( ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell") ))); 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 d74f897..e8f7d2a 100644 --- a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java +++ b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java @@ -6,152 +6,169 @@ import appeng.menu.implementations.UpgradeableMenu; import appeng.menu.slot.OptionalFakeSlot; import com.extendedae_plus.ae.parts.EntitySpeedTickerPart; import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen; -import com.extendedae_plus.config.ModConfig; 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 net.minecraft.client.Minecraft; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.Slot; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraftforge.registries.ForgeRegistries; -import java.util.List; - -// 实体加速器菜单,负责与客户端界面同步数据 +/** + * 实体加速器菜单,负责管理客户端与服务端的数据同步,处理加速卡、能量卡和目标方块的状态。 + */ 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(); + 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) {} - } - - // 当服务器数据同步到客户端时调用 + /** + * 服务端数据同步到客户端时调用,更新卡数量、目标状态和生效速度。 + */ @Override public void onServerDataSync() { super.onServerDataSync(); - // 重新统计实体加速卡和能量卡数量 + updateCardCounts(); // 更新卡数量 + updateTargetStatus(); // 更新目标方块的黑名单和倍率 + updateEffectiveSpeed(); // 计算生效速度 + updateNetworkEnergyStatus(); // 同步能量状态 + if (isClientSide()) { + refreshClientGui(); // 客户端刷新界面 + } + } + + /** + * 当槽位内容变化时调用,客户端更新卡数量和生效速度。 + * @param slot 发生变化的槽位 + */ + @Override + public void onSlotChange(Slot slot) { + super.onSlotChange(slot); + if (isClientSide()) { + updateCardCounts(); + updateEffectiveSpeed(); + refreshClientGui(); + } + } + + /** + * 广播数据变化,清理未启用槽位的显示堆栈。 + */ + @Override + public void broadcastChanges() { + for (Object o : this.slots) { + if (o instanceof OptionalFakeSlot fs && !fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) { + fs.clearStack(); // 清理未启用槽位的显示 + } + } + standardDetectAndSendChanges(); + } + + /** + * 更新加速卡和能量卡的数量。 + */ + private void updateCardCounts() { 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 = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString(); - for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(List.of(ModConfig.INSTANCE.entityTickerMultipliers))) { - 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) { - String blockId = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString(); - for (java.util.regex.Pattern p : ConfigParsingUtils.getCachedBlacklist(List.of(ModConfig.INSTANCE.entityTickerBlackList))) { - 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); + /** + * 更新目标方块的黑名单状态和倍率。 + */ + private void updateTargetStatus() { + BlockEntity target = getTargetBlockEntity(); + if (target == null) { + this.multiplier = 1.0; + this.targetBlacklisted = false; + return; } + String blockId = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString(); + this.multiplier = ConfigParsingUtils.getMultiplierForBlock(blockId); + this.targetBlacklisted = ConfigParsingUtils.isBlockBlacklisted(blockId); + } - // 从部件同步网络能量充足状态:仅在服务器端从部件读取,客户端应使用由 @GuiSync 同步过来的值 - try { - EntitySpeedTickerPart host = getHost(); - if (host != null && !isClientSide()) { - this.networkEnergySufficient = host.isNetworkEnergySufficient(); - } - } catch (Exception ignored) {} + /** + * 计算生效速度(考虑黑名单和卡数量)。 + */ + private void updateEffectiveSpeed() { + this.effectiveSpeed = targetBlacklisted ? 0 : (int) PowerUtils.computeProductWithCap(getUpgrades(), 8); + } - // 如果在客户端,刷新界面 - if (isClientSide()) { - if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) { - screen.refreshGui(); - } + /** + * 同步网络能量状态(仅服务端)。 + */ + private void updateNetworkEnergyStatus() { + if (!isClientSide() && getHost() != null) { + this.networkEnergySufficient = getHost().isNetworkEnergySufficient(); } } - // 当任意槽位发生变化时调用 - @Override - public void onSlotChange(net.minecraft.world.inventory.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(); - } + /** + * 客户端刷新界面。 + */ + private void refreshClientGui() { + if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) { + screen.refreshGui(); } } - @Override - public void broadcastChanges(){ - // 遍历所有槽位,清理未启用但有物品显示的 OptionalFakeSlot - for (Object o : this.slots) { - if (o instanceof OptionalFakeSlot fs) { - if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) { - fs.clearStack(); - } - } - } - // 调用标准的同步方法,通知监听者数据已更新 - this.standardDetectAndSendChanges(); + /** + * 获取目标方块实体。 + * @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 654d050..1867e6a 100644 --- a/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java +++ b/src/main/java/com/extendedae_plus/ae/parts/EntitySpeedTickerPart.java @@ -2,14 +2,18 @@ package com.extendedae_plus.ae.parts; import appeng.api.config.Actionable; import appeng.api.config.PowerMultiplier; +import appeng.api.config.YesNo; 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,8 +26,9 @@ import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu; import com.extendedae_plus.config.ModConfig; 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 com.extendedae_plus.util.Logger; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -36,88 +41,126 @@ 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.minecraftforge.fml.ModList; import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; +import java.lang.reflect.Method; /** - * EntitySpeedTickerPart 是一个可升级的 AE2 部件

- * 该部件可以加速目标方块实体的 tick 速率,消耗 AE 网络能量,并支持加速卡升级

- * 功能受Crazy AE2 Addons启发 + * 实体加速器部件,用于加速目标方块实体的 tick 速率,消耗 AE 网络能量,支持加速卡和能量卡升级。 + * 灵感来源于 Crazy AE2 Addons。 */ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject { - // 当前打开的菜单实例(如果有) - public EntitySpeedTickerMenu menu; - public static final ResourceLocation MODEL_BASE = new ResourceLocation( ExtendedAEPlus.MODID, "part/entity_speed_ticker_part"); @PartModels - public static final PartModel MODELS_OFF; + public static final PartModel MODELS_OFF = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off")); @PartModels - public static final PartModel MODELS_ON; + public static final PartModel MODELS_ON = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on")); @PartModels - public static final PartModel MODELS_HAS_CHANNEL; + public static final PartModel MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel")); + private static volatile boolean[] cachedAttempts; + + private static volatile Method cachedFEExtractMethod; + private static volatile boolean FE_UNAVAILABLE; + + // 静态块:初始化缓存 static { - MODELS_OFF = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off")); - MODELS_ON = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on")); - MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel")); + cachedAttempts = ModConfig.INSTANCE.prioritizeDiskEnergy ? new boolean[]{true, false, true} : new boolean[]{false, true, true}; + if (ModList.get().isLoaded("appflux")) { + try { + Class helperClass = Class.forName("com.extendedae_plus.util.FluxEnergyHelper"); + cachedFEExtractMethod = helperClass.getMethod( + "extractFE", + IEnergyService.class, + MEStorage.class, + long.class, + IActionSource.class + ); + FE_UNAVAILABLE = false; + } catch (Exception e) { + FE_UNAVAILABLE = true; + } + } } + public EntitySpeedTickerMenu menu; // 当前打开的菜单实例 + private boolean networkEnergySufficient = true; // 网络能量是否充足 + private int cachedSpeed = -1; // 缓存的加速倍率 + private int cachedEnergyCardCount = -1; // 缓存的能量卡数量 + private BlockEntity cachedTarget = null; + private BlockPos cachedTargetPos = null; + /** - * 构造函数,初始化部件并设置网络节点属性 + * 构造函数,初始化部件并设置网络节点属性。 + * * @param partItem 部件物品 */ public EntitySpeedTickerPart(IPartItem partItem) { super(partItem); - // 设置网络节点属性:需要通道、空闲功耗为1,并注册为 IGridTickable 服务 this.getMainNode() .setFlags(GridFlags.REQUIRE_CHANNEL) .setIdlePowerUsage(1) .addService(IGridTickable.class, this); + + // 注册可记忆的配置(YES/NO) + this.getConfigManager().registerSetting( + com.extendedae_plus.ae.api.config.Settings.ACCELERATE, + YesNo.YES + ); } - // 控制是否启用加速(默认启用) - private boolean accelerateEnabled = true; - // 标记网络中能量是否充足(用于 GUI 提示,默认充足) - private boolean networkEnergySufficient = true; + /** + * 更新缓存的 attempts 数组,由 ModConfig 调用。 + */ + public static void updateCachedAttempts(boolean prioritizeDiskEnergy) { + synchronized (EntitySpeedTickerPart.class) { + cachedAttempts = prioritizeDiskEnergy ? new boolean[]{true, false, true} : new boolean[]{false, true, true}; + } + } public boolean getAccelerateEnabled() { - return this.accelerateEnabled; + return this.getConfigManager().getSetting(com.extendedae_plus.ae.api.config.Settings.ACCELERATE) == YesNo.YES; + } + + /** + * 设置加速开关状态并通知菜单。 + * + * @param enabled 是否启用加速 + */ + public void setAccelerateEnabled(boolean enabled) { + this.getConfigManager().putSetting(com.extendedae_plus.ae.api.config.Settings.ACCELERATE, enabled ? YesNo.YES : YesNo.NO); + if (menu != null) { + menu.setAccelerateEnabled(enabled); + } } public boolean isNetworkEnergySufficient() { return this.networkEnergySufficient; } - public void setAccelerateEnabled(boolean accelerateEnabled) { - this.accelerateEnabled = accelerateEnabled; - } - /** - * 更新网络能量充足标记并在菜单存在且状态变化时触发同步 + * 更新网络能量充足状态并通知菜单。 + * * @param sufficient 是否能量充足 */ private void updateNetworkEnergySufficient(boolean sufficient) { - // 保持部件内部状态一致(部件为权威来源) this.networkEnergySufficient = sufficient; - if (this.menu != null) { - try { - // 使用菜单的封装方法更新并广播,以保持封装性 - this.menu.setNetworkEnergySufficient(sufficient); - } catch (Exception ignored) {} + if (menu != null) { + menu.setNetworkEnergySufficient(sufficient); } } /** - * 获取当前状态下的静态模型(用于渲染) + * 获取当前状态的渲染模型。 + * * @return 当前状态的模型 */ + @Override public IPartModel getStaticModels() { if (this.isActive() && this.isPowered()) { return MODELS_HAS_CHANNEL; @@ -129,15 +172,15 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka } /** - * 当玩家激活部件(右键)时调用,打开自定义菜单 + * 处理玩家右键激活部件,打开菜单。 + * * @param player 玩家 - * @param hand 手 - * @param pos 点击位置 - * @return 总是返回 true,表示激活成功 + * @param hand 手 + * @param pos 点击位置 + * @return 总是返回 true */ @Override public boolean onPartActivate(Player player, InteractionHand hand, Vec3 pos) { - // 仅在服务端打开菜单 if (!player.getCommandSenderWorld().isClientSide()) { MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this)); } @@ -145,7 +188,8 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka } /** - * 定义部件的碰撞箱(用于物理碰撞和渲染) + * 定义部件的碰撞箱。 + * * @param bch 碰撞辅助器 */ @Override @@ -155,160 +199,258 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka } /** - * 获取定时请求,决定本部件多久 tick 一次 + * 获取定时请求,指定 tick 频率。 + * * @param iGridNode 网络节点 * @return TickingRequest 对象 */ @Override public TickingRequest getTickingRequest(IGridNode iGridNode) { - // 每 1 tick 执行一次 return new TickingRequest(1, 1, false, true); } /** - * 当升级卡数量发生变化时调用,通知菜单更新 + * 当升级卡变化时通知菜单更新。 */ @Override public void upgradesChanged() { - if (this.menu != null) { - // 使用 AE2 风格:当升级发生变化时让菜单广播变化(槽/数据会被同步),客户端会基于槽内容重新计算并刷新界面 - this.menu.broadcastChanges(); + // 更新缓存的升级卡数量和加速倍率 + this.cachedEnergyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); + this.cachedSpeed = calculateSpeed(); + + if (menu != null) { + menu.broadcastChanges(); } } /** - * 网络定时回调,每次 tick 时调用 - * @param iGridNode 网络节点 - * @param ticksSinceLastCall 距离上次调用经过的 tick 数 - * @return TickRateModulation.IDLE 表示继续保持当前 tick 速率 + * 网络定时回调,处理目标方块实体的加速。 + * + * @param iGridNode 网络节点 + * @param ticksSinceLastCall 距离上次调用的 tick 数 + * @return TickRateModulation.IDLE */ @Override public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) { - // 如果部件的加速开关被关闭,则不进行加速(提前返回) - if (!this.getAccelerateEnabled()) { + if (!getAccelerateEnabled()) { return TickRateModulation.IDLE; } - - // 获取目标方块实体(本部件朝向的方块) - BlockEntity target = getLevel().getBlockEntity(getBlockEntity().getBlockPos().relative(getSide())); - // 仅在目标存在且部件处于激活状态时执行加速 - if (target != null && isActive()) { - ticker(target); + updateCachedTarget(); + if (cachedTarget != null && isActive()) { + ticker(cachedTarget); } return TickRateModulation.IDLE; } /** - * 以指定速度对目标方块实体进行 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 = Objects.requireNonNull(ForgeRegistries.BLOCKS.getKey(blockEntity.getBlockState().getBlock())).toString(); - - // 使用工具类的缓存接口(工具类内部负责懒加载/线程安全) - List compiledBlacklist = ConfigParsingUtils.getCachedBlacklist(List.of(ModConfig.INSTANCE.entityTickerBlackList)); - for (Pattern p : compiledBlacklist) { - if (p.matcher(blockId).matches()) return; - } - - // 获取该方块实体的 Ticker - @SuppressWarnings("unchecked") - BlockEntityTicker blockEntityTicker = this.getLevel() - .getBlockState(pos) - .getTicker(this.getLevel(), (BlockEntityType) blockEntity.getType()); - if (blockEntityTicker == null) return; - - // 使用集中定义的 CardDef 列表,支持以后添加等级或改倍率而无需修改此逻辑 - int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); - - // 使用已注册的单一 Item 计算已安装卡数量(总计,用于能耗计算) - int entitySpeedCardCount = getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get()); - - // 使用工具方法从槽位直接计算乘积并应用 cap(最多 8 张卡) - long product = PowerUtils.computeProductWithCapFromStacks(this.getUpgrades(), 8); - - // 如果没有任何实体加速卡,则不进行加速且不消耗额外能量(只保留部件的被动功耗) - if (entitySpeedCardCount <= 0) return; - - // 计算本次 tick 所需能量:使用工具类根据 product 计算最终能耗 - double requiredPower = PowerUtils.computeFinalPowerForProduct(product, energyCardCount); - - int speed = (int) product; - - double multiplier = 1.0; - for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(List.of(ModConfig.INSTANCE.entityTickerMultipliers))) { - if (me.pattern.matcher(blockId).matches()) { - multiplier = Math.max(multiplier, me.multiplier); - } - } - - requiredPower *= multiplier; - - // 先模拟提取以检查网络中是否有足够能量,再真正抽取 - double simulated = getMainNode().getGrid().getEnergyService() - .extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG); - if (simulated < requiredPower) { - updateNetworkEnergySufficient(false); // 能量不足 + String blockId = ForgeRegistries.BLOCKS.getKey(blockEntity.getBlockState().getBlock()).toString(); + if (ConfigParsingUtils.isBlockBlacklisted(blockId)) { return; } - double extractedPower = getMainNode().getGrid().getEnergyService() - .extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG); - if (extractedPower < requiredPower) { - updateNetworkEnergySufficient(false); // 能量不足 + BlockEntityTicker ticker = getTicker(blockEntity); + if (ticker == null) { return; } - updateNetworkEnergySufficient(true); // 能量充足 - // 计算加速倍数:基于 2 的次方,并把 8 张映射到最大 1024x(2^10) - // 已由 product 计算得到 speed;上面已在没有卡时提前返回 + if (cachedEnergyCardCount == -1 || cachedSpeed == -1) { + this.cachedEnergyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); + this.cachedSpeed = calculateSpeed(); + } - // 执行 tick 操作 - for (int i = 0; i < speed - 1; i++) { - blockEntityTicker.tick( - blockEntity.getLevel(), - blockEntity.getBlockPos(), - blockEntity.getBlockState(), - blockEntity - ); + if (cachedSpeed <= 0) { + return; + } + + double requiredPower = PowerUtils.getCachedPower(cachedSpeed, cachedEnergyCardCount) + * ConfigParsingUtils.getMultiplierForBlock(blockId); + if (!extractPower(requiredPower)) { + return; + } + + performTicks(blockEntity, ticker, cachedSpeed); + } + + /** + * 检查网络节点是否有效。 + * + * @return 是否可以执行 tick + */ + private boolean isValidForTicking() { + return getGridNode() != null && getMainNode() != null && getMainNode().getGrid() != null; + } + + /** + * 更新缓存的目标方块实体引用。 + */ + private void updateCachedTarget() { + BlockPos targetPos = getBlockEntity().getBlockPos().relative(getSide()); + if (!targetPos.equals(cachedTargetPos) || cachedTarget == null || cachedTarget.isRemoved() || + cachedTarget.getType() != getLevel().getBlockEntity(targetPos).getType()) { + cachedTargetPos = targetPos; + cachedTarget = getLevel().getBlockEntity(targetPos); } } /** - * 判断部件是否有自定义名称 - * @return 是否有自定义名称 + * 获取目标方块实体的 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 PowerUtils.computeProductWithCap(getUpgrades(), 8); + } + + /** + * 提取网络能量并更新状态,优先从 AE2 网络提取 AE 能量,不足时从磁盘提取 FE 能量。 + * + * @param requiredPower 所需能量(AE 单位) + * @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 preferDiskEnergy = ModConfig.INSTANCE.prioritizeDiskEnergy; + + // 如果优先磁盘能量,先尝试 FE + if (preferDiskEnergy && tryExtractFE(energyService, storage, requiredPower, source)) { + return true; + } + + // 先尝试 AE 能量 + 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); + + // 如果没成功,且不是优先磁盘能量,再尝试 FE + if (!preferDiskEnergy) { + return tryExtractFE(energyService, storage, requiredPower, source); + } + + return false; + } + + private boolean tryExtractFE(IEnergyService energyService, MEStorage storage, double requiredPower, IActionSource source) { + if (FE_UNAVAILABLE || cachedFEExtractMethod == null) { + updateNetworkEnergySufficient(false); + return false; + } + try { + long feRequired = (long) requiredPower << 1; // 1 AE = 2 FE + long feExtracted = (long) cachedFEExtractMethod.invoke(null, energyService, storage, feRequired, source); + if (feExtracted >= feRequired) { + updateNetworkEnergySufficient(true); + return true; + } + } catch (Exception e) { + // 如果反射调用失败,标记为不可用,避免下次继续尝试 + FE_UNAVAILABLE = true; + } + updateNetworkEnergySufficient(false); + return false; + } + + + /** + * 执行加速 tick 操作。 + * + * @param blockEntity 目标方块实体 + * @param ticker 方块实体 ticker + * @param speed 加速倍率 + */ + private void performTicks(T blockEntity, + BlockEntityTicker ticker, + int speed) { + for (int i = 0; i < speed - 1; i++) { + try { + ticker.tick( + blockEntity.getLevel(), + blockEntity.getBlockPos(), + blockEntity.getBlockState(), + blockEntity + ); + } catch (IllegalStateException e) { + // 捕获随机数生成器的多线程访问异常 + // 这通常发生在某些模组(如 Thermal)的机器使用随机数时 + // 由于加速导致在同一tick内多次访问随机数生成器而触发 ThreadingDetector + if (e.getMessage() != null && e.getMessage().contains("LegacyRandomSource")) { + // 记录警告并停止当前加速循环,避免崩溃 + Logger.EAP$LOGGER.warn( + "检测到方块实体 {} 在位置 {} 的随机数访问冲突,已停止本次加速以避免崩溃。" + + "建议将此方块类型添加到配置黑名单中。", + blockEntity.getType().toString(), + blockEntity.getBlockPos() + ); + break; // 停止后续的加速 tick + } else { + // 如果是其他类型的 IllegalStateException,继续抛出 + throw e; + } + } catch (Exception e) { + // 捕获其他可能的异常,防止崩溃 + Logger.EAP$LOGGER.error( + "在加速方块实体 {} 位置 {} 时发生错误: {}", + blockEntity.getType().toString(), + blockEntity.getBlockPos(), + e.getMessage(), + e + ); + break; // 停止后续的加速 tick + } + } + } + @Override public boolean hasCustomName() { return super.hasCustomName(); } - /** - * 获取部件的显示名称 - * @return 显示名称 - */ @Override public @NotNull Component getDisplayName() { return super.getDisplayName(); } /** - * 创建自定义菜单(GUI) - * @param containerId 容器ID + * 创建菜单实例。 + * + * @param containerId 容器ID * @param playerInventory 玩家背包 - * @param player 玩家 + * @param player 玩家 * @return 菜单实例 */ @Override @@ -319,11 +461,19 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka } /** - * 获取可用的升级卡槽数量 + * 获取升级卡槽数量。 + * * @return 升级卡槽数量 */ @Override protected int getUpgradeSlots() { return 8; } + + @Override + public void removeFromWorld() { + super.removeFromWorld(); + cachedTarget = null; + cachedTargetPos = null; + } } \ No newline at end of file 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 04fc9b1..7a4a169 100644 --- a/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java +++ b/src/main/java/com/extendedae_plus/ae/screen/EntitySpeedTickerScreen.java @@ -11,129 +11,115 @@ import appeng.util.Platform; import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.network.ToggleEntityTickerC2SPacket; -import com.extendedae_plus.util.PowerUtils; +import com.extendedae_plus.util.entitySpeed.PowerUtils; 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, - (btn, backwards) -> { - // 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传 - ModNetwork.CHANNEL.sendToServer(new ToggleEntityTickerC2SPacket()); - } + (btn, backwards) -> + ModNetwork.CHANNEL.sendToServer(new ToggleEntityTickerC2SPacket()) ) { @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(); } + /** + * 刷新界面显示。 + */ public void refreshGui() { textData(); } + /** + * 更新界面文本内容,包括加速状态、速度、能耗和倍率。 + */ private void textData() { - // 如果目标被黑名单禁止,则显示禁用状态并把数值显示为 0 - 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; - } + Map textContents = new HashMap<>(); + if (getMenu().targetBlacklisted || !getMenu().getAccelerateEnabled()) { + // 黑名单禁用或加速关闭时的默认显示 + 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.getCachedPower(effectiveSpeed, energyCardCount); + double remainingRatio = PowerUtils.getCachedRatio(energyCardCount); - int energyCardCount = getMenu().energyCardCount; - double multiplier = getMenu().multiplier; - int effectiveSpeed = getMenu().effectiveSpeed; - double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount); - double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount); - - if (!getMenu().networkEnergySufficient) { - setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient")); - }else { - setTextContent("enable", null); + 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))); } - 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/wireless/IWirelessEndpoint.java b/src/main/java/com/extendedae_plus/ae/wireless/IWirelessEndpoint.java similarity index 94% rename from src/main/java/com/extendedae_plus/wireless/IWirelessEndpoint.java rename to src/main/java/com/extendedae_plus/ae/wireless/IWirelessEndpoint.java index 15a991b..d21ebae 100644 --- a/src/main/java/com/extendedae_plus/wireless/IWirelessEndpoint.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/IWirelessEndpoint.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.wireless; +package com.extendedae_plus.ae.wireless; import appeng.api.networking.IGridNode; import net.minecraft.core.BlockPos; diff --git a/src/main/java/com/extendedae_plus/wireless/WirelessMasterLink.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java similarity index 78% rename from src/main/java/com/extendedae_plus/wireless/WirelessMasterLink.java rename to src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java index 4024b49..b055611 100644 --- a/src/main/java/com/extendedae_plus/wireless/WirelessMasterLink.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterLink.java @@ -1,6 +1,9 @@ -package com.extendedae_plus.wireless; +package com.extendedae_plus.ae.wireless; import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; /** * 主收发器端逻辑:负责在频率变化/加载时向注册中心登记唯一主端,卸载时反注册。 @@ -10,10 +13,16 @@ public class WirelessMasterLink { private final IWirelessEndpoint host; private long frequency; // 0 为未设置 private boolean registered; + @Nullable + private UUID placerId; // 放置者UUID public WirelessMasterLink(IWirelessEndpoint host) { this.host = host; } + + public void setPlacerId(@Nullable UUID placerId) { + this.placerId = placerId; + } public long getFrequency() { return frequency; } @@ -43,7 +52,8 @@ public class WirelessMasterLink { public boolean register() { ServerLevel level = host.getServerLevel(); if (level == null || frequency == 0L) return false; - boolean ok = WirelessMasterRegistry.register(level, frequency, host); + // placerId可以为null(公共收发器模式) + boolean ok = WirelessMasterRegistry.register(level, frequency, placerId, host); this.registered = ok; return ok; } @@ -51,7 +61,8 @@ public class WirelessMasterLink { public void unregister() { ServerLevel level = host.getServerLevel(); if (!registered || level == null || frequency == 0L) return; - WirelessMasterRegistry.unregister(level, frequency, host); + // placerId可以为null(公共收发器模式) + WirelessMasterRegistry.unregister(level, frequency, placerId, host); registered = false; } diff --git a/src/main/java/com/extendedae_plus/wireless/WirelessMasterRegistry.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java similarity index 54% rename from src/main/java/com/extendedae_plus/wireless/WirelessMasterRegistry.java rename to src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java index a5f35b8..d856d98 100644 --- a/src/main/java/com/extendedae_plus/wireless/WirelessMasterRegistry.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessMasterRegistry.java @@ -1,43 +1,68 @@ -package com.extendedae_plus.wireless; +package com.extendedae_plus.ae.wireless; import com.extendedae_plus.config.ModConfig; +import com.extendedae_plus.util.WirelessTeamUtil; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.UUID; /** - * 无线主端注册中心:按 维度 + 频率 唯一注册一个主收发器端点。 + * 无线主端注册中心:按 维度 + 频率 + 所有者 唯一注册一个主收发器端点。 * 从端通过本注册中心按频率查找主端,实现一对多连接。 + * 所有者隔离:有FTBTeams时同队共享,没有时每个玩家独立。 + * 公共模式:placerId为null时使用公共UUID,所有人都能访问(向下兼容旧版本)。 */ public final class WirelessMasterRegistry { private WirelessMasterRegistry() {} private static final Map> MASTERS = new HashMap<>(); + + /** + * 公共收发器UUID(用于没有设置所有者的收发器) + * 所有placerId为null的收发器都使用这个UUID,实现公共访问 + */ + public static final UUID PUBLIC_NETWORK_UUID = new UUID(0, 0); - public static synchronized boolean register(ServerLevel level, long frequency, IWirelessEndpoint endpoint) { + public static synchronized boolean register(ServerLevel level, long frequency, @Nullable UUID placerId, IWirelessEndpoint endpoint) { Objects.requireNonNull(level, "level"); Objects.requireNonNull(endpoint, "endpoint"); if (frequency == 0L) return false; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + + // 获取网络所有者UUID + // placerId为null时使用公共UUID(向下兼容旧版本收发器) + UUID ownerUUID = placerId != null + ? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId) + : PUBLIC_NETWORK_UUID; + + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + cleanupIfCleared(key); var existing = MASTERS.get(key); var existingVal = existing == null ? null : existing.get(); if (existingVal != null && !existingVal.isEndpointRemoved()) { - // 同维度同频率已经有主端 + // 同维度同频率同所有者已经有主端 return false; } MASTERS.put(key, new WeakReference<>(endpoint)); return true; } - public static synchronized void unregister(ServerLevel level, long frequency, IWirelessEndpoint endpoint) { + public static synchronized void unregister(ServerLevel level, long frequency, @Nullable UUID placerId, IWirelessEndpoint endpoint) { if (frequency == 0L || level == null) return; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + + UUID ownerUUID = placerId != null + ? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId) + : PUBLIC_NETWORK_UUID; + + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + var ref = MASTERS.get(key); if (ref != null) { var cur = ref.get(); @@ -47,9 +72,15 @@ public final class WirelessMasterRegistry { } } - public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency) { + public static synchronized IWirelessEndpoint get(ServerLevel level, long frequency, @Nullable UUID placerId) { if (frequency == 0L || level == null) return null; - final Key key = new Key(useGlobal() ? null : level.dimension(), frequency); + + UUID ownerUUID = placerId != null + ? WirelessTeamUtil.getNetworkOwnerUUID(level, placerId) + : PUBLIC_NETWORK_UUID; + + final Key key = new Key(useGlobal() ? null : level.dimension(), frequency, ownerUUID); + cleanupIfCleared(key); var ref = MASTERS.get(key); return ref == null ? null : ref.get(); @@ -66,9 +97,12 @@ public final class WirelessMasterRegistry { return ModConfig.INSTANCE.wirelessCrossDimEnable; } - private record Key(ResourceKey dim, long freq) { - @Override public String toString() { - return (dim == null ? "*" : dim.location().toString()) + "#" + freq; + private record Key(@Nullable ResourceKey dim, long freq, UUID owner) { + @Override + public String toString() { + return (dim == null ? "*" : dim.location().toString()) + + "#" + freq + + "@" + owner; } } } diff --git a/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java b/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java similarity index 91% rename from src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java rename to src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java index 84035fd..c97ab58 100644 --- a/src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/WirelessSlaveLink.java @@ -1,22 +1,26 @@ -package com.extendedae_plus.wireless; +package com.extendedae_plus.ae.wireless; import appeng.api.networking.GridHelper; import appeng.api.networking.IGridNode; import appeng.me.service.helpers.ConnectionWrapper; import com.extendedae_plus.config.ModConfig; import net.minecraft.server.level.ServerLevel; +import org.jetbrains.annotations.Nullable; import java.util.Objects; +import java.util.UUID; /** * 从收发器连接器: * - 通过频率查找同维度主收发器; * - 校验距离(<= ModConfigs.WIRELESS_MAX_RANGE); - * - 动态创建/销毁 AE2 连接(GridConnection),实现“一主多从”。 + * - 动态创建/销毁 AE2 连接(GridConnection),实现"一主多从"。 */ public class WirelessSlaveLink { private final IWirelessEndpoint host; private long frequency; // 0 未设置 + @Nullable + private UUID placerId; // 放置者UUID private ConnectionWrapper connection = new ConnectionWrapper(null); private boolean shutdown = true; @@ -25,6 +29,10 @@ public class WirelessSlaveLink { public WirelessSlaveLink(IWirelessEndpoint host) { this.host = Objects.requireNonNull(host); } + + public void setPlacerId(@Nullable UUID placerId) { + this.placerId = placerId; + } public void setFrequency(long frequency) { if (this.frequency != frequency) { @@ -60,7 +68,8 @@ public class WirelessSlaveLink { return; } - IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency); + // placerId可以为null(公共收发器模式) + IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency, placerId); shutdown = false; distance = 0.0D; diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/ae/wireless/endpoint/GenericNodeEndpointImpl.java similarity index 93% rename from src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java rename to src/main/java/com/extendedae_plus/ae/wireless/endpoint/GenericNodeEndpointImpl.java index 84cd191..3653e12 100644 --- a/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/endpoint/GenericNodeEndpointImpl.java @@ -1,7 +1,7 @@ -package com.extendedae_plus.wireless.endpoint; +package com.extendedae_plus.ae.wireless.endpoint; import appeng.api.networking.IGridNode; -import com.extendedae_plus.wireless.IWirelessEndpoint; +import com.extendedae_plus.ae.wireless.IWirelessEndpoint; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/ae/wireless/endpoint/InterfaceNodeEndpointImpl.java similarity index 92% rename from src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java rename to src/main/java/com/extendedae_plus/ae/wireless/endpoint/InterfaceNodeEndpointImpl.java index 6736f4c..f6dbfff 100644 --- a/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java +++ b/src/main/java/com/extendedae_plus/ae/wireless/endpoint/InterfaceNodeEndpointImpl.java @@ -1,8 +1,8 @@ -package com.extendedae_plus.wireless.endpoint; +package com.extendedae_plus.ae.wireless.endpoint; import appeng.api.networking.IGridNode; import appeng.helpers.InterfaceLogicHost; -import com.extendedae_plus.wireless.IWirelessEndpoint; +import com.extendedae_plus.ae.wireless.IWirelessEndpoint; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; diff --git a/src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java b/src/main/java/com/extendedae_plus/api/IAdvancedBlocking.java similarity index 74% rename from src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java rename to src/main/java/com/extendedae_plus/api/IAdvancedBlocking.java index 52b8aaa..1895a7d 100644 --- a/src/main/java/com/extendedae_plus/api/AdvancedBlockingHolder.java +++ b/src/main/java/com/extendedae_plus/api/IAdvancedBlocking.java @@ -1,6 +1,6 @@ package com.extendedae_plus.api; -public interface AdvancedBlockingHolder { +public interface IAdvancedBlocking { boolean eap$getAdvancedBlocking(); void eap$setAdvancedBlocking(boolean value); diff --git a/src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java b/src/main/java/com/extendedae_plus/api/IExPatternButtonsAccessor.java similarity index 87% rename from src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java rename to src/main/java/com/extendedae_plus/api/IExPatternButtonsAccessor.java index d97f448..ac4d1ec 100644 --- a/src/main/java/com/extendedae_plus/api/ExPatternButtonsAccessor.java +++ b/src/main/java/com/extendedae_plus/api/IExPatternButtonsAccessor.java @@ -3,7 +3,7 @@ package com.extendedae_plus.api; /** * 由 {@code GuiExPatternProviderMixin} 实现,用于从通用的 Screen Mixin 中更新按钮布局。 */ -public interface ExPatternButtonsAccessor { +public interface IExPatternButtonsAccessor { /** * 在每帧调用以维护扩展样板供应器右侧按钮的可见性、重注册(窗口尺寸变化)与定位。 */ diff --git a/src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java b/src/main/java/com/extendedae_plus/api/IExPatternPageAccessor.java similarity index 82% rename from src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java rename to src/main/java/com/extendedae_plus/api/IExPatternPageAccessor.java index fca633f..c368e26 100644 --- a/src/main/java/com/extendedae_plus/api/ExPatternPageAccessor.java +++ b/src/main/java/com/extendedae_plus/api/IExPatternPageAccessor.java @@ -3,6 +3,6 @@ package com.extendedae_plus.api; /** * 由 GuiExPatternProviderMixin 实现,用于在客户端侧提供当前页号,避免反射读取 AE2 内部字段失败。 */ -public interface ExPatternPageAccessor { +public interface IExPatternPageAccessor { int eap$getCurrentPage(); } diff --git a/src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java b/src/main/java/com/extendedae_plus/api/IPatternProviderMenuAdvancedSync.java similarity index 60% rename from src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java rename to src/main/java/com/extendedae_plus/api/IPatternProviderMenuAdvancedSync.java index f43f52c..5d5147d 100644 --- a/src/main/java/com/extendedae_plus/api/PatternProviderMenuAdvancedSync.java +++ b/src/main/java/com/extendedae_plus/api/IPatternProviderMenuAdvancedSync.java @@ -1,5 +1,5 @@ package com.extendedae_plus.api; -public interface PatternProviderMenuAdvancedSync { +public interface IPatternProviderMenuAdvancedSync { boolean eap$getAdvancedBlockingSynced(); } diff --git a/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java b/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java deleted file mode 100644 index a9d1492..0000000 --- a/src/main/java/com/extendedae_plus/api/PatternProviderMenuDoublingSync.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.extendedae_plus.api; - -public interface PatternProviderMenuDoublingSync { - boolean eap$getSmartDoublingSynced(); -} diff --git a/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java b/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java deleted file mode 100644 index 5c18658..0000000 --- a/src/main/java/com/extendedae_plus/api/SmartDoublingAwarePattern.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.extendedae_plus.api; - -public interface SmartDoublingAwarePattern { - boolean eap$allowScaling(); - void eap$setAllowScaling(boolean allow); -} diff --git a/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java b/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java deleted file mode 100644 index b5af4ef..0000000 --- a/src/main/java/com/extendedae_plus/api/SmartDoublingHolder.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.extendedae_plus.api; - -public interface SmartDoublingHolder { - boolean eap$getSmartDoubling(); - void eap$setSmartDoubling(boolean value); -} diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/api/bridge/IInterfaceWirelessLinkBridge.java similarity index 92% rename from src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java rename to src/main/java/com/extendedae_plus/api/bridge/IInterfaceWirelessLinkBridge.java index d200f77..f83aea5 100644 --- a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java +++ b/src/main/java/com/extendedae_plus/api/bridge/IInterfaceWirelessLinkBridge.java @@ -1,9 +1,9 @@ -package com.extendedae_plus.bridge; +package com.extendedae_plus.api.bridge; /** * 非 mixin 包下的桥接接口,供 mixin 进行 instanceof 检测和回调。 */ -public interface InterfaceWirelessLinkBridge { +public interface IInterfaceWirelessLinkBridge { void eap$updateWirelessLink(); /** diff --git a/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java b/src/main/java/com/extendedae_plus/api/bridge/IUpgradableMenu.java similarity index 89% rename from src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java rename to src/main/java/com/extendedae_plus/api/bridge/IUpgradableMenu.java index 470ccd5..4bdbbb7 100644 --- a/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java +++ b/src/main/java/com/extendedae_plus/api/bridge/IUpgradableMenu.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.bridge; +package com.extendedae_plus.api.bridge; import appeng.api.upgrades.IUpgradeInventory; import appeng.menu.ToolboxMenu; diff --git a/src/main/java/com/extendedae_plus/api/smartDoubling/IPatternProviderMenuDoublingSync.java b/src/main/java/com/extendedae_plus/api/smartDoubling/IPatternProviderMenuDoublingSync.java new file mode 100644 index 0000000..4fd840c --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/smartDoubling/IPatternProviderMenuDoublingSync.java @@ -0,0 +1,7 @@ +package com.extendedae_plus.api.smartDoubling; + +public interface IPatternProviderMenuDoublingSync { + boolean eap$getSmartDoublingSynced(); + + int eap$getScalingLimit(); +} diff --git a/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java new file mode 100644 index 0000000..ff491d1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java @@ -0,0 +1,10 @@ +package com.extendedae_plus.api.smartDoubling; + +public interface ISmartDoublingAwarePattern { + boolean eap$allowScaling(); + void eap$setAllowScaling(boolean allow); + + // 翻倍限制:0 表示无限制 + int eap$getMultiplierLimit(); + void eap$setMultiplierLimit(int limit); +} diff --git a/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingHolder.java b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingHolder.java new file mode 100644 index 0000000..f956208 --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingHolder.java @@ -0,0 +1,11 @@ +package com.extendedae_plus.api.smartDoubling; + +public interface ISmartDoublingHolder { + boolean eap$getSmartDoubling(); + + void eap$setSmartDoubling(boolean value); + + int eap$getProviderSmartDoublingLimit(); + + void eap$setProviderSmartDoublingLimit(int limit); +} diff --git a/src/main/java/com/extendedae_plus/client/InputEvents.java b/src/main/java/com/extendedae_plus/client/InputEvents.java index 6a921a4..f99765f 100644 --- a/src/main/java/com/extendedae_plus/client/InputEvents.java +++ b/src/main/java/com/extendedae_plus/client/InputEvents.java @@ -8,8 +8,8 @@ import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.integration.jei.JeiRuntimeProxy; import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor; import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; -import com.extendedae_plus.network.OpenCraftFromJeiC2SPacket; import com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket; +import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import mezz.jei.api.ingredients.ITypedIngredient; import net.minecraft.client.Minecraft; @@ -17,8 +17,8 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.common.Mod; import org.lwjgl.glfw.GLFW; import java.util.Optional; @@ -33,10 +33,7 @@ public final class InputEvents { if (!ModList.get().isLoaded("jei")) { return; } - // 若 JEI 运行时尚未就绪,跳过 - if (JeiRuntimeProxy.get() == null) { - return; - } + // 注意:不要在 try/catch 之外直接访问 JEI 运行时,避免类加载崩溃 // 优先处理:Shift + 左键(拉取或下单) if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) { try { @@ -103,9 +100,7 @@ public final class InputEvents { if (!ModList.get().isLoaded("jei")) { return; } - if (JeiRuntimeProxy.get() == null) { - return; - } + // 注意:不要在 try/catch 之外直接访问 JEI 运行时,避免类加载崩溃 if (event.getKeyCode() != GLFW.GLFW_KEY_F) return; // 仅当鼠标确实悬停在 JEI 配料上时触发 diff --git a/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java b/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java new file mode 100644 index 0000000..a952edd --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/event/ChannelCardClientHandler.java @@ -0,0 +1,45 @@ +package com.extendedae_plus.client.event; + +import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.init.ModNetwork; +import com.extendedae_plus.network.ChannelCardBindPacket; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +/** + * 频道卡客户端事件处理器 + * 处理左键空气事件并发送网络包到服务端 + */ +@Mod.EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class ChannelCardClientHandler { + + /** + * 左键空气事件(仅客户端) + */ + @SubscribeEvent + public static void onLeftClickEmpty(PlayerInteractEvent.LeftClickEmpty event) { + Player player = event.getEntity(); + + // 只处理潜行 + if (!player.isShiftKeyDown()) { + return; + } + + // 检查是否手持频道卡 + ItemStack stack = event.getItemStack(); + if (stack.getItem() != ModItems.CHANNEL_CARD.get()) { + return; + } + + // 发送网络包到服务端 + InteractionHand hand = event.getHand(); + ModNetwork.CHANNEL.sendToServer(new ChannelCardBindPacket(hand)); + } +} + diff --git a/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java index 49c36a8..87aff55 100644 --- a/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java +++ b/src/main/java/com/extendedae_plus/client/screen/GlobalProviderModesScreen.java @@ -2,7 +2,7 @@ package com.extendedae_plus.client.screen; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.menu.NetworkPatternControllerMenu; -import com.extendedae_plus.network.GlobalToggleProviderModesC2SPacket; +import com.extendedae_plus.network.provider.GlobalToggleProviderModesC2SPacket; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.network.chat.Component; diff --git a/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java b/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java new file mode 100644 index 0000000..595664f --- /dev/null +++ b/src/main/java/com/extendedae_plus/client/ui/FrequencyInputScreen.java @@ -0,0 +1,122 @@ +package com.extendedae_plus.client.ui; + +import com.extendedae_plus.init.ModNetwork; +import com.extendedae_plus.network.SetWirelessFrequencyC2SPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; + +/** + * 频率输入界面 + * 用于通过输入框设置无线收发器的频率 + */ +public class FrequencyInputScreen extends Screen { + private final BlockPos pos; + private final long currentFrequency; + private EditBox frequencyInput; + + public FrequencyInputScreen(BlockPos pos, long currentFrequency) { + super(Component.translatable("extendedae_plus.screen.frequency_input.title")); + this.pos = pos; + this.currentFrequency = currentFrequency; + } + + @Override + protected void init() { + int centerX = this.width / 2; + int centerY = this.height / 2; + + // 频率输入框 + this.frequencyInput = new EditBox(this.font, centerX - 100, centerY - 10, 200, 20, + Component.translatable("extendedae_plus.screen.frequency_input.field")); + this.frequencyInput.setValue(String.valueOf(currentFrequency)); + this.frequencyInput.setMaxLength(19); // long最大值是19位 + this.frequencyInput.setFilter(text -> { + // 只允许输入数字 + if (text.isEmpty()) return true; + try { + Long.parseLong(text); + return true; + } catch (NumberFormatException e) { + return false; + } + }); + this.addRenderableWidget(this.frequencyInput); + this.setInitialFocus(this.frequencyInput); + + // 确认按钮 + Button confirmButton = Button.builder(Component.translatable("extendedae_plus.screen.frequency_input.confirm"), + button -> onConfirm()) + .bounds(centerX - 105, centerY + 20, 100, 20) + .build(); + this.addRenderableWidget(confirmButton); + + // 取消按钮 + Button cancelButton = Button.builder(Component.translatable("gui.cancel"), + button -> onClose()) + .bounds(centerX + 5, centerY + 20, 100, 20) + .build(); + this.addRenderableWidget(cancelButton); + } + + private void onConfirm() { + try { + String text = this.frequencyInput.getValue(); + if (text.isEmpty()) { + // 空值视为0 + text = "0"; + } + long frequency = Long.parseLong(text); + if (frequency < 0) { + frequency = 0; + } + + // 发送数据包到服务端 + ModNetwork.CHANNEL.sendToServer(new SetWirelessFrequencyC2SPacket(pos, frequency)); + this.onClose(); + } catch (NumberFormatException e) { + // 输入无效,不做处理 + } + } + + @Override + public void render(net.minecraft.client.gui.GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + this.renderBackground(guiGraphics); + super.render(guiGraphics, mouseX, mouseY, partialTicks); + + // 绘制标题 + guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, this.height / 2 - 40, 0xFFFFFF); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + // 按回车键确认 + if (keyCode == 257 || keyCode == 335) { // ENTER or NUMPAD_ENTER + onConfirm(); + return true; + } + // 按ESC键取消 + if (keyCode == 256) { // ESC + onClose(); + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public void tick() { + super.tick(); + if (this.frequencyInput != null) { + this.frequencyInput.tick(); + } + } +} + diff --git a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java index 1984717..6b8b229 100644 --- a/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java +++ b/src/main/java/com/extendedae_plus/client/ui/ProviderSelectScreen.java @@ -183,10 +183,29 @@ public class ProviderSelectScreen extends Screen { } } + /** + * 将服务器发送的名称(可能是 Component JSON)反序列化为本地化文本 + */ + private String deserializeComponentName(String name) { + try { + // 如果名称是 JSON 格式的 Component,反序列化后获取本地化文本 + if (name.startsWith("{") || name.startsWith("\"")) { + Component component = Component.Serializer.fromJson(name); + if (component != null) { + return component.getString(); + } + } + } catch (Exception e) { + // 如果不是 JSON 或解析失败,使用原始字符串 + } + return name; + } + private String buildLabel(int idx) { String name = fNames.get(idx); int totalSlots = fTotalSlots.get(idx); int count = fCount.get(idx); + // 不显示具体 id,显示合并统计:名称(总空位)x数量 return name + " (" + totalSlots + ") x" + count; } @@ -215,7 +234,11 @@ public class ProviderSelectScreen extends Screen { String name = names.get(i); long id = ids.get(i); int slots = emptySlots.get(i); - Group g = map.computeIfAbsent(name, k -> new Group()); + + // 将 Component JSON 转换为本地化文本用于分组键 + String groupKey = deserializeComponentName(name); + + Group g = map.computeIfAbsent(groupKey, k -> new Group()); g.count++; g.totalSlots += Math.max(0, slots); // 挑选空位最多的作为代表 id;若并列,保留先到者 diff --git a/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java b/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java index 30004e8..8cfe537 100644 --- a/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java +++ b/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java @@ -1,6 +1,6 @@ package com.extendedae_plus.compat; -import com.extendedae_plus.util.ExtendedAELogger; +import com.extendedae_plus.util.Logger; import net.minecraftforge.fml.ModList; /** @@ -27,7 +27,7 @@ public class CompatibilityTest { if (appfluxExists) { } else { } - + } /** diff --git a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java index 9aa03ff..c6eab7a 100644 --- a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java +++ b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java @@ -2,7 +2,6 @@ package com.extendedae_plus.compat; import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeableObject; -import appeng.api.upgrades.UpgradeInventories; import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.widgets.ToolboxPanel; import appeng.client.gui.widgets.UpgradesPanel; @@ -11,7 +10,7 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.menu.AEBaseMenu; import appeng.menu.SlotSemantics; import appeng.menu.ToolboxMenu; -import com.extendedae_plus.util.ExtendedAELogger; +import com.extendedae_plus.util.Logger; import net.minecraft.network.chat.Component; import net.minecraft.world.inventory.Slot; import net.minecraftforge.fml.ModList; @@ -41,12 +40,7 @@ public class UpgradeSlotCompat { */ public static boolean shouldEnableUpgradeSlots() { boolean appfluxExists = isAppfluxPresent(); - - if (appfluxExists) { - return false; - } else { - return true; - } + return !appfluxExists; } /** @@ -89,7 +83,7 @@ public class UpgradeSlotCompat { setupUpgradesMethod.setAccessible(true); setupUpgradesMethod.invoke(menu, upgradeableHost.getUpgrades()); } catch (Exception e) { - ExtendedAELogger.LOGGER.error("反射调用setupUpgrades失败", e); + Logger.EAP$LOGGER.error("反射调用setupUpgrades失败", e); return false; } @@ -97,12 +91,10 @@ public class UpgradeSlotCompat { if (menu instanceof IUpgradeableMenuCompat compatMenu) { compatMenu.setCompatToolbox(toolbox); } - - ExtendedAELogger.LOGGER.debug("成功为PatternProviderMenu初始化升级功能"); return true; } } catch (Exception e) { - ExtendedAELogger.LOGGER.error("初始化PatternProviderMenu升级功能时出错", e); + Logger.EAP$LOGGER.error("初始化PatternProviderMenu升级功能时出错", e); } return false; @@ -144,8 +136,6 @@ public class UpgradeSlotCompat { ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); addMethod.invoke(widgets, "toolbox", toolboxPanel); } - - ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板"); return true; } catch (NoSuchMethodException e) { // 尝试其他可能的方法签名 @@ -170,17 +160,15 @@ public class UpgradeSlotCompat { ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); putMethod.invoke(widgets, "toolbox", toolboxPanel); } - - ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板(使用put方法)"); return true; } catch (Exception e2) { - ExtendedAELogger.LOGGER.error("反射调用widgets方法失败", e2); + Logger.EAP$LOGGER.error("反射调用widgets方法失败", e2); return false; } } } } catch (Exception e) { - ExtendedAELogger.LOGGER.error("为PatternProviderScreen添加升级面板时出错", e); + Logger.EAP$LOGGER.error("为PatternProviderScreen添加升级面板时出错", e); } return false; @@ -199,7 +187,7 @@ public class UpgradeSlotCompat { list.addAll(appeng.api.upgrades.Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem())); } } catch (Exception e) { - ExtendedAELogger.LOGGER.error("获取兼容升级列表时出错", e); + Logger.EAP$LOGGER.error("获取兼容升级列表时出错", e); } return list; diff --git a/src/main/java/com/extendedae_plus/config/ModConfig.java b/src/main/java/com/extendedae_plus/config/ModConfig.java index f55ee29..7505220 100644 --- a/src/main/java/com/extendedae_plus/config/ModConfig.java +++ b/src/main/java/com/extendedae_plus/config/ModConfig.java @@ -1,18 +1,30 @@ package com.extendedae_plus.config; import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.ae.parts.EntitySpeedTickerPart; +import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils; +import com.extendedae_plus.util.entitySpeed.PowerUtils; import dev.toma.configuration.Configuration; +import dev.toma.configuration.client.IValidationHandler; import dev.toma.configuration.config.Config; import dev.toma.configuration.config.Configurable; import dev.toma.configuration.config.format.ConfigFormats; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static com.extendedae_plus.util.Logger.EAP$LOGGER; @Config(id = ExtendedAEPlus.MODID) public final class ModConfig { public static ModConfig INSTANCE; private static final Object lock = new Object(); - + private static final long DEBOUNCE_INTERVAL = 1000; // 防抖间隔,单位:毫秒 + private static final AtomicLong lastUpdateTime = new AtomicLong(0); public static void init() { synchronized (lock) { @@ -21,6 +33,7 @@ public final class ModConfig { } } } + @Configurable @Configurable.Comment(value = { "扩展样板供应器总槽位容量的倍率。", @@ -95,6 +108,7 @@ public final class ModConfig { }) @Configurable.Range(min = 0, max = Integer.MAX_VALUE) @Configurable.Synchronized + @Configurable.ValueUpdateCallback(method = "onEntityTickerCostUpdate") public int entityTickerCost = 512; @Configurable @@ -103,9 +117,8 @@ public final class ModConfig { "格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'" }) @Configurable.Synchronized - public String[] entityTickerBlackList = { - - }; + @Configurable.ValueUpdateCallback(method = "onEntityTickerBlackListUpdate") + public String[] entityTickerBlackList = {}; @Configurable @Configurable.Comment(value = { @@ -113,9 +126,8 @@ public final class ModConfig { "支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。" }) @Configurable.Synchronized - public String[] entityTickerMultipliers = { - - }; + @Configurable.ValueUpdateCallback(method = "onEntityTickerMultipliersUpdate") + public String[] entityTickerMultipliers = {}; @Configurable @Configurable.Comment(value = { @@ -133,4 +145,51 @@ public final class ModConfig { @Configurable.Synchronized public boolean restrictCraftingPatternToMolecular = false; + @Configurable + @Configurable.Comment(value = { + "是否优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)", + "开启后,将优先尝试从磁盘提取FE能量;反之优先消耗AE网络中的能量" + }) + @Configurable.Synchronized + @Configurable.ValueUpdateCallback(method = "onPrioritizeDiskEnergyUpdate") + public boolean prioritizeDiskEnergy = true; + + private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); + private static ScheduledFuture pendingPowerTask; + private static final Object POWER_LOCK = new Object(); + + private void onEntityTickerCostUpdate(int newValue, IValidationHandler handler) { + synchronized (POWER_LOCK) { + if (pendingPowerTask != null) { + pendingPowerTask.cancel(false); + } + pendingPowerTask = EXECUTOR.schedule(() -> { + synchronized (PowerUtils.class) { + PowerUtils.initializeCaches(); + } + }, 1000, TimeUnit.MILLISECONDS); // 1000ms 防抖 + } + } + + + private void onEntityTickerBlackListUpdate(String[] newValue, IValidationHandler handler) { + synchronized (ConfigParsingUtils.class) { + EAP$LOGGER.info("onEntityTickerBlackListUpdate"); + ConfigParsingUtils.reload(); + } + } + + private void onEntityTickerMultipliersUpdate(String[] newValue, IValidationHandler handler) { + synchronized (ConfigParsingUtils.class) { + EAP$LOGGER.info("onEntityTickerMultipliersUpdate"); + ConfigParsingUtils.reload(); + } + } + + private void onPrioritizeDiskEnergyUpdate(boolean newValue, dev.toma.configuration.client.IValidationHandler handler) { + synchronized (EntitySpeedTickerPart.class) { + EAP$LOGGER.info("onPrioritizeDiskEnergyUpdate"); + EntitySpeedTickerPart.updateCachedAttempts(newValue); + } + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java index 0f0c9fc..8041566 100644 --- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java +++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlock.java @@ -1,11 +1,15 @@ package com.extendedae_plus.content.wireless; +import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.init.ModBlockEntities; +import com.extendedae_plus.init.ModItems; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Block; @@ -15,6 +19,9 @@ import net.minecraft.world.level.block.entity.BlockEntityTicker; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; public class WirelessTransceiverBlock extends Block implements EntityBlock { public WirelessTransceiverBlock(Properties props) { @@ -27,28 +34,71 @@ public class WirelessTransceiverBlock extends Block implements EntityBlock { } @Override - public void attack(BlockState state, Level level, BlockPos pos, Player player) { - // 潜行左键:减频(-1 或 -10) - if (!level.isClientSide && player.isShiftKeyDown()) { + public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + super.setPlacedBy(level, pos, state, placer, stack); + if (!level.isClientSide && placer instanceof Player player) { BlockEntity be = level.getBlockEntity(pos); if (be instanceof WirelessTransceiverBlockEntity te) { - if (te.isLocked()) { - player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true); + te.setPlacerId(player.getUUID(), player.getName().getString()); + } + } + } + + @Override + public void attack(BlockState state, Level level, BlockPos pos, Player player) { + if (!level.isClientSide) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof WirelessTransceiverBlockEntity te) { + ItemStack mainHand = player.getMainHandItem(); + + // 潜行左键频道卡:写入频道卡信息到收发器 + if (player.isShiftKeyDown() && mainHand.getItem() == ModItems.CHANNEL_CARD.get()) { + handleChannelCardBinding(te, mainHand, player); super.attack(state, level, pos, player); return; } - int step = 1; - if (player.getMainHandItem().is(Items.REDSTONE_TORCH)) step = 10; - if (player.getMainHandItem().is(Items.STICK)) step = 10; - long f = te.getFrequency(); - f -= step; - if (f < 0) f = 0; - te.setFrequency(f); - player.displayClientMessage(Component.literal("频道:" + te.getFrequency()), true); + + // 潜行左键(其他物品):减频(-1 或 -10) + if (player.isShiftKeyDown()) { + if (te.isLocked()) { + player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true); + super.attack(state, level, pos, player); + return; + } + int step = 1; + if (mainHand.is(Items.REDSTONE_TORCH)) step = 10; + if (mainHand.is(Items.STICK)) step = 10; + long f = te.getFrequency(); + f -= step; + if (f < 0) f = 0; + te.setFrequency(f); + player.displayClientMessage(Component.literal("频道:" + te.getFrequency()), true); + } } } super.attack(state, level, pos, player); } + + /** + * 处理频道卡绑定到收发器 + */ + private void handleChannelCardBinding(WirelessTransceiverBlockEntity te, ItemStack channelCard, Player player) { + UUID cardOwner = ChannelCardItem.getOwnerUUID(channelCard); + + if (cardOwner != null) { + // 写入频道卡的所有者到收发器 + String teamName = ChannelCardItem.getTeamName(channelCard); + te.setPlacerId(cardOwner, teamName); + player.displayClientMessage( + Component.literal("已将收发器绑定至:" + (teamName != null ? teamName : cardOwner.toString().substring(0, 8))), + true + ); + } else { + // 频道卡未绑定所有者,使用当前玩家 + te.setPlacerId(player.getUUID(), player.getName().getString()); + player.displayClientMessage(Component.literal("频道卡未绑定,已使用当前玩家"), true); + } + } @Override public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { diff --git a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java index 09b843d..ca61349 100644 --- a/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java +++ b/src/main/java/com/extendedae_plus/content/wireless/WirelessTransceiverBlockEntity.java @@ -3,11 +3,11 @@ package com.extendedae_plus.content.wireless; import appeng.api.networking.*; import appeng.api.util.AECableType; import appeng.blockentity.AEBaseBlockEntity; +import com.extendedae_plus.ae.wireless.IWirelessEndpoint; +import com.extendedae_plus.ae.wireless.WirelessMasterLink; +import com.extendedae_plus.ae.wireless.WirelessSlaveLink; import com.extendedae_plus.init.ModBlockEntities; import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.wireless.IWirelessEndpoint; -import com.extendedae_plus.wireless.WirelessMasterLink; -import com.extendedae_plus.wireless.WirelessSlaveLink; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable; import java.util.EnumSet; import java.util.Objects; +import java.util.UUID; /** * 无线收发器方块实体(骨架): @@ -25,6 +26,7 @@ import java.util.Objects; * - 频率设置; * - 集成 AE2 节点; * - 集成无线主/从逻辑。 + * - 支持FTBTeams队伍隔离(软依赖) */ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost { @@ -33,6 +35,11 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements private long frequency = 1L; private boolean masterMode = false; private boolean locked = false; + + @Nullable + private UUID placerId; // 放置者UUID,用于队伍隔离 + @Nullable + private String placerName; // 放置者名称,用于显示 private WirelessMasterLink masterLink; private WirelessSlaveLink slaveLink; @@ -96,6 +103,43 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements } /* ===================== 公共方法(交互调用) ===================== */ + + /** + * 设置放置者UUID和名称(在方块放置时调用) + */ + public void setPlacerId(@Nullable UUID placerId, @Nullable String placerName) { + if (this.placerId != null && !this.placerId.equals(placerId)) { + // 如果所有者改变,需要重新注册 + if (this.masterMode) { + masterLink.onUnloadOrRemove(); + } else { + slaveLink.onUnloadOrRemove(); + } + } + this.placerId = placerId; + this.placerName = placerName; + this.masterLink.setPlacerId(placerId); + this.slaveLink.setPlacerId(placerId); + setChanged(); + } + + /** + * 仅设置UUID(兼容旧代码) + */ + public void setPlacerId(@Nullable UUID placerId) { + setPlacerId(placerId, null); + } + + @Nullable + public UUID getPlacerId() { + return placerId; + } + + @Nullable + public String getPlacerName() { + return placerName; + } + public long getFrequency() { return frequency; } @@ -192,6 +236,12 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements tag.putLong("frequency", frequency); tag.putBoolean("master", masterMode); tag.putBoolean("locked", locked); + if (placerId != null) { + tag.putUUID("placerId", placerId); + } + if (placerName != null) { + tag.putString("placerName", placerName); + } if (managedNode != null) { managedNode.saveToNBT(tag); } @@ -203,6 +253,16 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements this.frequency = tag.getLong("frequency"); this.masterMode = tag.getBoolean("master"); this.locked = tag.getBoolean("locked"); + + if (tag.hasUUID("placerId")) { + this.placerId = tag.getUUID("placerId"); + this.masterLink.setPlacerId(this.placerId); + this.slaveLink.setPlacerId(this.placerId); + } + + if (tag.contains("placerName")) { + this.placerName = tag.getString("placerName"); + } if (managedNode != null) { managedNode.loadFromNBT(tag); diff --git a/src/main/java/com/extendedae_plus/hooks/WrenchHook.java b/src/main/java/com/extendedae_plus/hooks/WrenchHook.java index f098b45..1976a88 100644 --- a/src/main/java/com/extendedae_plus/hooks/WrenchHook.java +++ b/src/main/java/com/extendedae_plus/hooks/WrenchHook.java @@ -2,9 +2,11 @@ package com.extendedae_plus.hooks; import appeng.util.InteractionUtil; import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.client.ui.FrequencyInputScreen; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; import appeng.block.crafting.CraftingUnitBlock; import appeng.blockentity.crafting.CraftingBlockEntity; +import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; @@ -14,6 +16,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.eventbus.api.SubscribeEvent; @@ -101,4 +105,37 @@ public final class WrenchHook { } } } + + @SubscribeEvent + public static void onPlayerLeftClickBlock(PlayerInteractEvent.LeftClickBlock event) { + var player = event.getEntity(); + var level = event.getLevel(); + var pos = event.getPos(); + + // 非旁观者 + if (player.isSpectator()) { + return; + } + + ItemStack stack = player.getMainHandItem(); + // 潜行 + 扳手 + 无线收发器:打开频率输入界面 + if (InteractionUtil.isInAlternateUseMode(player) && InteractionUtil.canWrenchRotate(stack)) { + BlockEntity be = level.getBlockEntity(pos); + if (be instanceof WirelessTransceiverBlockEntity te) { + if (level.isClientSide) { + // 客户端打开GUI + openFrequencyInputScreen(pos, te.getFrequency()); + } + // 轻微反馈音效 + level.playSound(player, pos, SoundEvents.UI_BUTTON_CLICK.get(), SoundSource.BLOCKS, 0.5F, 1.0F); + + event.setCanceled(true); + } + } + } + + @OnlyIn(Dist.CLIENT) + private static void openFrequencyInputScreen(net.minecraft.core.BlockPos pos, long currentFrequency) { + Minecraft.getInstance().setScreen(new FrequencyInputScreen(pos, currentFrequency)); + } } diff --git a/src/main/java/com/extendedae_plus/init/ModNetwork.java b/src/main/java/com/extendedae_plus/init/ModNetwork.java index e281d81..3480871 100644 --- a/src/main/java/com/extendedae_plus/init/ModNetwork.java +++ b/src/main/java/com/extendedae_plus/init/ModNetwork.java @@ -2,6 +2,11 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.network.*; +import com.extendedae_plus.network.crafting.CraftingMonitorJumpC2SPacket; +import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket; +import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket; +import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket; +import com.extendedae_plus.network.provider.*; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkRegistry; @@ -67,6 +72,12 @@ public class ModNetwork { .consumerNetworkThread(SetPatternHighlightS2CPacket::handle) .add(); + CHANNEL.messageBuilder(com.extendedae_plus.network.SetBlockHighlightS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT) + .encoder(com.extendedae_plus.network.SetBlockHighlightS2CPacket::encode) + .decoder(com.extendedae_plus.network.SetBlockHighlightS2CPacket::decode) + .consumerNetworkThread(com.extendedae_plus.network.SetBlockHighlightS2CPacket::handle) + .add(); + CHANNEL.messageBuilder(SetProviderPageS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT) .encoder(SetProviderPageS2CPacket::encode) .decoder(SetProviderPageS2CPacket::decode) @@ -85,6 +96,12 @@ public class ModNetwork { .consumerNetworkThread(ToggleSmartDoublingC2SPacket::handle) .add(); + CHANNEL.messageBuilder(com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket::encode) + .decoder(com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket::decode) + .consumerNetworkThread(com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket::handle) + .add(); + CHANNEL.messageBuilder(GlobalToggleProviderModesC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) .encoder(GlobalToggleProviderModesC2SPacket::encode) .decoder(GlobalToggleProviderModesC2SPacket::decode) @@ -120,6 +137,18 @@ public class ModNetwork { .decoder(CraftingMonitorOpenProviderC2SPacket::decode) .consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle) .add(); + + CHANNEL.messageBuilder(ChannelCardBindPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(ChannelCardBindPacket::encode) + .decoder(ChannelCardBindPacket::decode) + .consumerNetworkThread(ChannelCardBindPacket::handle) + .add(); + + CHANNEL.messageBuilder(SetWirelessFrequencyC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER) + .encoder(SetWirelessFrequencyC2SPacket::encode) + .decoder(SetWirelessFrequencyC2SPacket::decode) + .consumerNetworkThread(SetWirelessFrequencyC2SPacket::handle) + .add(); } private static int nextId() { return id++; } diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java index c7b9c33..747cecb 100644 --- a/src/main/java/com/extendedae_plus/init/UpgradeCards.java +++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java @@ -6,14 +6,8 @@ import appeng.core.definitions.AEItems; import appeng.core.definitions.AEParts; import appeng.core.localization.GuiText; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER_PART; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE_PART; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE_PART; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_IMPORT_BUS; -import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_EXPORT_BUS; + +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.*; /** * diff --git a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java index 2505db5..3f8f1d4 100644 --- a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java +++ b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverJadePluginComponents.java @@ -66,6 +66,39 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr tooltip.add(Component.literal((usable ? "设备在线" : "设备离线"))); } } + }, + CHANNELS("wt_channels") { + @Override + protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) { + if (data.contains("usedChannels") && data.contains("maxChannels")) { + int usedChannels = data.getInt("usedChannels"); + int maxChannels = data.getInt("maxChannels"); + // 参考AE2的显示方式 + if (maxChannels <= 0) { + // 无限频道或未设置 + tooltip.add(Component.translatable("extendedae_plus.tooltip.channels", usedChannels)); + } else { + // 显示 "已使用/最大" + tooltip.add(Component.translatable("extendedae_plus.tooltip.channels_of", usedChannels, maxChannels)); + } + } + } + }, + OWNER("wt_owner") { + @Override + protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) { + if (data.contains("ownerName")) { + String ownerName = data.getString("ownerName"); + tooltip.add(Component.translatable("extendedae_plus.tooltip.owner", ownerName)); + } else if (data.contains("placerId")) { + // 有placerId但没有名称,显示UUID + java.util.UUID placerId = data.getUUID("placerId"); + tooltip.add(Component.translatable("extendedae_plus.tooltip.owner", placerId.toString().substring(0, 8) + "...")); + } else { + // 没有所有者信息(公共收发器) + tooltip.add(Component.translatable("extendedae_plus.tooltip.owner.public")); + } + } }; private final ResourceLocation uid; diff --git a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java index 7786e1c..d5ea951 100644 --- a/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java +++ b/src/main/java/com/extendedae_plus/integration/jade/WirelessTransceiverProvider.java @@ -2,11 +2,13 @@ package com.extendedae_plus.integration.jade; import appeng.api.networking.IGrid; import appeng.api.networking.IGridNode; +import com.extendedae_plus.ae.wireless.IWirelessEndpoint; +import com.extendedae_plus.ae.wireless.WirelessMasterRegistry; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; -import com.extendedae_plus.wireless.IWirelessEndpoint; -import com.extendedae_plus.wireless.WirelessMasterRegistry; +import com.extendedae_plus.util.WirelessTeamUtil; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import snownee.jade.api.BlockAccessor; import snownee.jade.api.IServerDataProvider; @@ -40,11 +42,46 @@ public enum WirelessTransceiverProvider implements IServerDataProvider proxy = Class.forName("com.extendedae_plus.integration.jei.JeiRuntimeProxy"); + var m = proxy.getDeclaredMethod("get"); + Object rt = m.invoke(null); + return (IJeiRuntime) rt; + } catch (Throwable ignored) { + return null; + } + } + + public static List> getBookmarkList() { + IJeiRuntime rt = getRuntime(); + if (rt == null) return Collections.emptyList(); + IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); + if (bookmarkOverlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList bookmarkList = accessor.eap$getBookmarkList(); + return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList(); + } + return Collections.emptyList(); + } + + public static void addBookmark(ItemStack stack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.add(bookmark); // add 内部会自动保存到配置 + }); + } + } + + public static void addBookmark(FluidStack fluidStack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.add(bookmark); // add 内部会自动保存到配置 + }); + } + } + + /** + * 如果存在 Mekanism/appmek,则将 Mekanism 化学堆栈添加到 JEI 书签。 + */ + public static void addBookmark(Object chemicalStack) { + if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return; + + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + try { + if (chemicalStack == null) return; + + // Determine Mekanism JEI ingredient type constant by runtime class name + String clsName = chemicalStack.getClass().getName(); + String mekanismJeiClass = "mekanism.client.jei.MekanismJEI"; + Class jeiCls = Class.forName(mekanismJeiClass); + Field typeField = getField(clsName, jeiCls); + + if (typeField == null) return; + Object typeConst = typeField.get(null); + + // Use ingredient manager reflectively to create a typed ingredient + Object ingredientManager = rt.getIngredientManager(); + Method createTypedIngredient = ingredientManager.getClass().getMethod("createTypedIngredient", IIngredientType.class, Object.class); + Object opt = createTypedIngredient.invoke(ingredientManager, typeConst, chemicalStack); + if (!(opt instanceof Optional typedOpt)) return; + if (typedOpt.isPresent()) { + Object typed = typedOpt.get(); + // Find a compatible static create(...) method on IngredientBookmark where + // the second parameter is assignable from the actual ingredientManager instance. + Method createMethod = null; + for (Method m : IngredientBookmark.class.getMethods()) { + if (!m.getName().equals("create")) continue; + Class[] params = m.getParameterTypes(); + if (params.length != 2) continue; + // first param should accept the typed ingredient + boolean firstOk = params[0].isAssignableFrom(typed.getClass()) || params[0].isAssignableFrom(ITypedIngredient.class); + boolean secondOk = params[1].isAssignableFrom(ingredientManager.getClass()); + if (firstOk && secondOk) { + createMethod = m; + break; + } + } + if (createMethod != null) { + Object bookmark = createMethod.invoke(null, typed, ingredientManager); + if (bookmark != null) { + list.add((IngredientBookmark) bookmark); + } + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + private static @Nullable Field getField(String clsName, Class jeiCls) throws NoSuchFieldException { + Field typeField = null; + if ("mekanism.api.chemical.gas.GasStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_GAS"); + } else if ("mekanism.api.chemical.infuse.InfusionStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_INFUSION"); + } else if ("mekanism.api.chemical.pigment.PigmentStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_PIGMENT"); + } else if ("mekanism.api.chemical.slurry.SlurryStack".equals(clsName)) { + typeField = jeiCls.getField("TYPE_SLURRY"); + } + return typeField; + } + + /** + * 从 JEI 书签移除物品 + */ + public static void removeBookmark(ItemStack stack) { + IJeiRuntime rt = getRuntime(); + if (rt == null) return; + + IBookmarkOverlay overlay = rt.getBookmarkOverlay(); + if (overlay instanceof BookmarkOverlayAccessor accessor) { + BookmarkList list = accessor.eap$getBookmarkList(); + Optional> typedOpt = rt.getIngredientManager() + .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); + typedOpt.ifPresent(typed -> { + IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); + list.remove(bookmark); + }); + } + } +} diff --git a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java index bc5276e..38c3541 100644 --- a/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java +++ b/src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java @@ -1,24 +1,16 @@ package com.extendedae_plus.integration.jei; -import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor; import mezz.jei.api.constants.VanillaTypes; -import mezz.jei.api.forge.ForgeTypes; -import mezz.jei.api.ingredients.IIngredientType; import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.runtime.IBookmarkOverlay; import mezz.jei.api.runtime.IIngredientListOverlay; import mezz.jei.api.runtime.IJeiRuntime; -import mezz.jei.gui.bookmarks.BookmarkList; -import mezz.jei.gui.bookmarks.IngredientBookmark; -import mezz.jei.gui.overlay.elements.IElement; import net.minecraft.world.item.ItemStack; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fml.ModList; import org.spongepowered.asm.mixin.Pseudo; import javax.annotation.Nullable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -131,48 +123,35 @@ public final class JeiRuntimeProxy { } /** - * 获取JEI书签列表 + * 获取 JEI 书签列表。为避免在未安装 JEI GUI 时崩溃,使用反射委托到桥接类。 */ public static List> getBookmarkList() { - IJeiRuntime rt = RUNTIME; - if (rt == null) return Collections.emptyList(); - IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); - if (bookmarkOverlay instanceof BookmarkOverlayAccessor accessor) { - BookmarkList bookmarkList = accessor.eap$getBookmarkList(); - return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList(); + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("getBookmarkList"); + @SuppressWarnings("unchecked") + List> list = (List>) m.invoke(null); + return list == null ? Collections.emptyList() : list; + } catch (Throwable ignored) { + return Collections.emptyList(); } - return Collections.emptyList(); } public static void addBookmark(ItemStack stack) { - IJeiRuntime rt = RUNTIME; - if (rt == null) return; - - IBookmarkOverlay overlay = rt.getBookmarkOverlay(); - if (overlay instanceof BookmarkOverlayAccessor accessor) { - BookmarkList list = accessor.eap$getBookmarkList(); - Optional> typedOpt = rt.getIngredientManager() - .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); - typedOpt.ifPresent(typed -> { - IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); - list.add(bookmark); // add 内部会自动保存到配置 - }); + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("addBookmark", ItemStack.class); + m.invoke(null, stack); + } catch (Throwable ignored) { } } public static void addBookmark(FluidStack fluidStack) { - IJeiRuntime rt = RUNTIME; - if (rt == null) return; - - IBookmarkOverlay overlay = rt.getBookmarkOverlay(); - if (overlay instanceof BookmarkOverlayAccessor accessor) { - BookmarkList list = accessor.eap$getBookmarkList(); - Optional> typedOpt = rt.getIngredientManager() - .createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack); - typedOpt.ifPresent(typed -> { - IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); - list.add(bookmark); // add 内部会自动保存到配置 - }); + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("addBookmark", FluidStack.class); + m.invoke(null, fluidStack); + } catch (Throwable ignored) { } } @@ -180,91 +159,25 @@ public final class JeiRuntimeProxy { * 如果存在 Mekanism/appmek,则将 Mekanism 化学堆栈添加到 JEI 书签。 */ public static void addBookmark(Object chemicalStack) { - if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return; - - IJeiRuntime rt = RUNTIME; - if (rt == null) return; - - IBookmarkOverlay overlay = rt.getBookmarkOverlay(); - if (overlay instanceof BookmarkOverlayAccessor accessor) { - BookmarkList list = accessor.eap$getBookmarkList(); - try { - if (chemicalStack == null) return; - - // Determine Mekanism JEI ingredient type constant by runtime class name - String clsName = chemicalStack.getClass().getName(); - String mekanismJeiClass = "mekanism.client.jei.MekanismJEI"; - Class jeiCls = Class.forName(mekanismJeiClass); - Field typeField = getField(clsName, jeiCls); - - if (typeField == null) return; - Object typeConst = typeField.get(null); - - // Use ingredient manager reflectively to create a typed ingredient - Object ingredientManager = rt.getIngredientManager(); - Method createTypedIngredient = ingredientManager.getClass().getMethod("createTypedIngredient", IIngredientType.class, Object.class); - Object opt = createTypedIngredient.invoke(ingredientManager, typeConst, chemicalStack); - if (!(opt instanceof Optional typedOpt)) return; - if (typedOpt.isPresent()) { - Object typed = typedOpt.get(); - // Find a compatible static create(...) method on IngredientBookmark where - // the second parameter is assignable from the actual ingredientManager instance. - Method createMethod = null; - for (Method m : IngredientBookmark.class.getMethods()) { - if (!m.getName().equals("create")) continue; - Class[] params = m.getParameterTypes(); - if (params.length != 2) continue; - // first param should accept the typed ingredient - boolean firstOk = params[0].isAssignableFrom(typed.getClass()) || params[0].isAssignableFrom(ITypedIngredient.class); - boolean secondOk = params[1].isAssignableFrom(ingredientManager.getClass()); - if (firstOk && secondOk) { - createMethod = m; - break; - } - } - if (createMethod != null) { - Object bookmark = createMethod.invoke(null, typed, ingredientManager); - if (bookmark != null) { - list.add((IngredientBookmark) bookmark); - } - } - } - } catch (Throwable e) { - e.printStackTrace(); - } + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("addBookmark", Object.class); + m.invoke(null, chemicalStack); + } catch (Throwable ignored) { } } - private static @Nullable Field getField(String clsName, Class jeiCls) throws NoSuchFieldException { - Field typeField = null; - if ("mekanism.api.chemical.gas.GasStack".equals(clsName)) { - typeField = jeiCls.getField("TYPE_GAS"); - } else if ("mekanism.api.chemical.infuse.InfusionStack".equals(clsName)) { - typeField = jeiCls.getField("TYPE_INFUSION"); - } else if ("mekanism.api.chemical.pigment.PigmentStack".equals(clsName)) { - typeField = jeiCls.getField("TYPE_PIGMENT"); - } else if ("mekanism.api.chemical.slurry.SlurryStack".equals(clsName)) { - typeField = jeiCls.getField("TYPE_SLURRY"); - } - return typeField; - } + // Note: helper methods moved to bridge to avoid referencing JEI GUI at class load time. /** - * 从 JEI 书签移除物品 + * 从 JEI 书签移除物品(反射委托) */ public static void removeBookmark(ItemStack stack) { - IJeiRuntime rt = RUNTIME; - if (rt == null) return; - - IBookmarkOverlay overlay = rt.getBookmarkOverlay(); - if (overlay instanceof BookmarkOverlayAccessor accessor) { - BookmarkList list = accessor.eap$getBookmarkList(); - Optional> typedOpt = rt.getIngredientManager() - .createTypedIngredient(VanillaTypes.ITEM_STACK, stack); - typedOpt.ifPresent(typed -> { - IngredientBookmark bookmark = IngredientBookmark.create(typed, rt.getIngredientManager()); - list.remove(bookmark); - }); + try { + Class bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge"); + var m = bridge.getMethod("removeBookmark", ItemStack.class); + m.invoke(null, stack); + } catch (Throwable ignored) { } } } diff --git a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java index 595bf1c..dbf7799 100644 --- a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java +++ b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java @@ -1,6 +1,5 @@ package com.extendedae_plus.mixin; -import net.minecraftforge.fml.ModList; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -27,7 +26,6 @@ public class MixinConditions implements IMixinConfigPlugin { public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { // 对于升级相关的Mixin,检查appflux是否存在 if (mixinClassName.contains("PatternProviderMenuUpgradesMixin") || - mixinClassName.contains("PatternProviderScreenUpgradesMixin") || mixinClassName.contains("PatternProviderLogicUpgradesMixin") || mixinClassName.contains("PatternProviderLogicHostUpgradesMixin")) { diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java index 0773388..fb482b9 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java @@ -1,7 +1,7 @@ package com.extendedae_plus.mixin.advancedae; import appeng.api.crafting.IPatternDetails; -import com.extendedae_plus.content.ScaledProcessingPattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; 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 index 7db6811..dbc985f 100644 --- 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 @@ -1,17 +1,19 @@ 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.api.IPatternProviderMenuAdvancedSync; +import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync; 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 com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket; +import com.extendedae_plus.network.provider.ToggleAdvancedBlockingC2SPacket; +import com.extendedae_plus.network.provider.ToggleSmartDoublingC2SPacket; +import com.extendedae_plus.util.GuiUtil; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.pedroksl.advanced_ae.client.gui.AdvPatternProviderScreen; @@ -22,7 +24,10 @@ 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; +import java.util.List; + +import static com.extendedae_plus.util.GuiUtil.createToggle; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; /** * 为高级ae样板供应器界面添加“高级阻挡模式”按钮。 @@ -31,116 +36,160 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; */ @Mixin(AdvPatternProviderScreen.class) public abstract class AdvPatternProviderScreenMixin extends AEBaseScreen { + // 高级阻挡模式切换按钮 + @Unique private SettingToggleButton eap$AdvancedBlockingToggle; + // 智能翻倍切换按钮 + @Unique private SettingToggleButton eap$SmartDoublingToggle; + // 智能翻倍上限输入框 + @Unique private EditBox eap$PerProviderLimitInput; - @Unique - private SettingToggleButton eap$AdvancedBlockingToggle; - - @Unique - private boolean eap$AdvancedBlockingEnabled = false; - - @Unique - private SettingToggleButton eap$SmartDoublingToggle; - - @Unique - private boolean eap$SmartDoublingEnabled = false; + // 当前高级阻挡模式是否启用 + @Unique private boolean eap$AdvancedBlockingEnabled = false; + // 当前智能翻倍是否启用 + @Unique private boolean eap$SmartDoublingEnabled = false; + // 当前智能翻倍上限 + @Unique private int eap$PerProviderScalingLimit = 0; public AdvPatternProviderScreenMixin(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } - @Inject(method = "", at = @At("RETURN")) - private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { - // 使用 @GuiSync 初始化 + /** 同步服务端状态(初始化时调用) */ + @Unique + private void eap$syncInitialState(AdvPatternProviderMenu menu) { try { - if (menu instanceof PatternProviderMenuAdvancedSync sync) { + // 同步高级阻挡和智能翻倍的服务端状态 + if (menu instanceof IPatternProviderMenuAdvancedSync 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(); + if (menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit(); } } catch (Throwable t) { - LOGGER.error("Error initializing smart doubling sync", t); + EAP$LOGGER.error("Error initializing 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)同步布尔值,保持按钮状态一致 + /** 创建并添加按钮和输入框 */ + @Unique + private void eap$createWidgets() { + // 高级阻挡 + this.eap$AdvancedBlockingToggle = createToggle( + eap$AdvancedBlockingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket()), + () -> { + var t = Component.literal("智能阻挡"); + var line = eap$AdvancedBlockingEnabled + ? Component.literal("已启用:对于同一种配方将不再阻挡 (需要启用原版阻挡模式)") + : Component.literal("已禁用:建议开启以获得更智能的阻挡行为"); + return List.of(t, line); + } + ); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$AdvancedBlockingToggle); + + // 智能翻倍 + this.eap$SmartDoublingToggle = createToggle( + eap$SmartDoublingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket()), + () -> { + var t = Component.literal("智能翻倍"); + var line = eap$SmartDoublingEnabled + ? Component.literal("已启用:根据请求量对处理样板进行智能缩放") + : Component.literal("已禁用:按原始样板数量进行发配"); + return List.of(t, line); + } + ); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$SmartDoublingToggle); + + // 缩放上限输入框(使用 GuiUtil 抽离) + this.eap$PerProviderLimitInput = GuiUtil.createPerProviderLimitInput(this.font, this.eap$PerProviderScalingLimit, limit -> { + this.eap$PerProviderScalingLimit = limit; + ModNetwork.CHANNEL.sendToServer(new SetPerProviderScalingLimitC2SPacket(limit)); + }); + this.addRenderableWidget(this.eap$PerProviderLimitInput); + } + + /* ---------------------------- 注入点 ---------------------------- */ + @Inject(method = "", at = @At("RETURN")) + private void eap$onInit(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 初始化时同步服务端状态并创建控件 + eap$syncInitialState(menu); + eap$createWidgets(); + } + + /** 每帧刷新:从服务端同步状态并更新 UI */ @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); + private void eap$updateBeforeRender(CallbackInfo ci) { + // 每帧刷新按钮和输入框状态 + eap$updateToggles(); + eap$updateLimitInput(); + } + + /* ---------------------------- 刷新逻辑 ---------------------------- */ + /** + * 刷新切换按钮的状态(与服务端同步) + */ + @Unique + private void eap$updateToggles() { + if (this.eap$AdvancedBlockingToggle != null && this.menu instanceof IPatternProviderMenuAdvancedSync sync) { + this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + } + if (this.eap$SmartDoublingToggle != null && this.menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + } + } + + /** + * 刷新输入框的内容和可见性 + */ + @Unique + private void eap$updateLimitInput() { + if (this.eap$PerProviderLimitInput == null) return; + + int remoteLimit = this.eap$PerProviderScalingLimit; + // 获取服务端最新的 scaling limit + if (this.menu instanceof IPatternProviderMenuDoublingSync sync) { + remoteLimit = sync.eap$getScalingLimit(); } - 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); + boolean focused = this.eap$PerProviderLimitInput.isFocused(); + // 如果未聚焦且服务端有变化,则同步显示 + if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) { + this.eap$PerProviderScalingLimit = remoteLimit; + this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit)); } - if ((Object) this instanceof GuiExPatternProvider) { - try { - ((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); - } catch (Throwable t) { - LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); + if (this.eap$SmartDoublingEnabled) { + // 智能翻倍启用时,确保输入框可见 + if (!this.renderables.contains(this.eap$PerProviderLimitInput)) { + this.addRenderableWidget(this.eap$PerProviderLimitInput); } + // 未聚焦且内容为空时,显示0 + if (!focused && this.eap$PerProviderLimitInput.getValue().trim().isEmpty()) { + this.eap$PerProviderLimitInput.setValue("0"); + } + + // 定位输入框到智能翻倍按钮左侧 + Button ref = eap$SmartDoublingToggle; + if (ref != null) { + int ex = ref.getX() - this.eap$PerProviderLimitInput.getWidth() - 5; + int ey = ref.getY() + 2; + this.eap$PerProviderLimitInput.setX(ex); + this.eap$PerProviderLimitInput.setY(ey); + } + + // 设置 tooltip + String cur = this.eap$PerProviderLimitInput.getValue(); + if (cur.isBlank()) cur = "0"; + this.eap$PerProviderLimitInput.setTooltip(Tooltip.create(Component.literal("单样物品发配数量上限: " + cur))); + } else { + // 智能翻倍未启用时,移除输入框 + this.removeWidget(this.eap$PerProviderLimitInput); } } } 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 index ea36939..955f996 100644 --- 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 @@ -1,20 +1,23 @@ 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.api.IPatternProviderMenuAdvancedSync; +import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync; 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 com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket; +import com.extendedae_plus.network.provider.ToggleAdvancedBlockingC2SPacket; +import com.extendedae_plus.network.provider.ToggleSmartDoublingC2SPacket; +import com.extendedae_plus.util.GuiUtil; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; 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.AdvPatternProviderMenu; import net.pedroksl.advanced_ae.gui.advpatternprovider.SmallAdvPatternProviderMenu; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -22,7 +25,10 @@ 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; +import java.util.List; + +import static com.extendedae_plus.util.GuiUtil.createToggle; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; /** * 为高级ae样板供应器界面添加“高级阻挡模式”按钮。 @@ -31,116 +37,160 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; */ @Mixin(SmallAdvPatternProviderScreen.class) public abstract class SmallAdvPatternProviderScreenMixin extends AEBaseScreen { + // 高级阻挡模式切换按钮 + @Unique private SettingToggleButton eap$AdvancedBlockingToggle; + // 智能翻倍切换按钮 + @Unique private SettingToggleButton eap$SmartDoublingToggle; + // 智能翻倍上限输入框 + @Unique private EditBox eap$PerProviderLimitInput; - @Unique - private SettingToggleButton eap$AdvancedBlockingToggle; - - @Unique - private boolean eap$AdvancedBlockingEnabled = false; - - @Unique - private SettingToggleButton eap$SmartDoublingToggle; - - @Unique - private boolean eap$SmartDoublingEnabled = false; + // 当前高级阻挡模式是否启用 + @Unique private boolean eap$AdvancedBlockingEnabled = false; + // 当前智能翻倍是否启用 + @Unique private boolean eap$SmartDoublingEnabled = false; + // 当前智能翻倍上限 + @Unique private int eap$PerProviderScalingLimit = 0; public SmallAdvPatternProviderScreenMixin(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } - @Inject(method = "", at = @At("RETURN")) - private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { - // 使用 @GuiSync 初始化 + /** 同步服务端状态(初始化时调用) */ + @Unique + private void eap$syncInitialState(AdvPatternProviderMenu menu) { try { - if (menu instanceof PatternProviderMenuAdvancedSync sync) { + // 同步高级阻挡和智能翻倍的服务端状态 + if (menu instanceof IPatternProviderMenuAdvancedSync 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(); + if (menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit(); } } catch (Throwable t) { - LOGGER.error("Error initializing smart doubling sync", t); + EAP$LOGGER.error("Error initializing 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)同步布尔值,保持按钮状态一致 + /** 创建并添加按钮和输入框 */ + @Unique + private void eap$createWidgets() { + // 高级阻挡 + this.eap$AdvancedBlockingToggle = createToggle( + eap$AdvancedBlockingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket()), + () -> { + var t = Component.literal("智能阻挡"); + var line = eap$AdvancedBlockingEnabled + ? Component.literal("已启用:对于同一种配方将不再阻挡 (需要启用原版阻挡模式)") + : Component.literal("已禁用:建议开启以获得更智能的阻挡行为"); + return List.of(t, line); + } + ); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$AdvancedBlockingToggle); + + // 智能翻倍 + this.eap$SmartDoublingToggle = createToggle( + eap$SmartDoublingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket()), + () -> { + var t = Component.literal("智能翻倍"); + var line = eap$SmartDoublingEnabled + ? Component.literal("已启用:根据请求量对处理样板进行智能缩放") + : Component.literal("已禁用:按原始样板数量进行发配"); + return List.of(t, line); + } + ); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$SmartDoublingToggle); + + // 缩放上限输入框(使用 GuiUtil 抽离) + this.eap$PerProviderLimitInput = GuiUtil.createPerProviderLimitInput(this.font, this.eap$PerProviderScalingLimit, limit -> { + this.eap$PerProviderScalingLimit = limit; + ModNetwork.CHANNEL.sendToServer(new SetPerProviderScalingLimitC2SPacket(limit)); + }); + this.addRenderableWidget(this.eap$PerProviderLimitInput); + } + + /* ---------------------------- 注入点 ---------------------------- */ + @Inject(method = "", at = @At("RETURN")) + private void eap$onInit(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 初始化时同步服务端状态并创建控件 + eap$syncInitialState(menu); + eap$createWidgets(); + } + + /** 每帧刷新:从服务端同步状态并更新 UI */ @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); + private void eap$updateBeforeRender(CallbackInfo ci) { + // 每帧刷新按钮和输入框状态 + eap$updateToggles(); + eap$updateLimitInput(); + } + + /* ---------------------------- 刷新逻辑 ---------------------------- */ + /** + * 刷新切换按钮的状态(与服务端同步) + */ + @Unique + private void eap$updateToggles() { + if (this.eap$AdvancedBlockingToggle != null && this.menu instanceof IPatternProviderMenuAdvancedSync sync) { + this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + } + if (this.eap$SmartDoublingToggle != null && this.menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + } + } + + /** + * 刷新输入框的内容和可见性 + */ + @Unique + private void eap$updateLimitInput() { + if (this.eap$PerProviderLimitInput == null) return; + + int remoteLimit = this.eap$PerProviderScalingLimit; + // 获取服务端最新的 scaling limit + if (this.menu instanceof IPatternProviderMenuDoublingSync sync) { + remoteLimit = sync.eap$getScalingLimit(); } - 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); + boolean focused = this.eap$PerProviderLimitInput.isFocused(); + // 如果未聚焦且服务端有变化,则同步显示 + if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) { + this.eap$PerProviderScalingLimit = remoteLimit; + this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit)); } - if ((Object) this instanceof GuiExPatternProvider) { - try { - ((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); - } catch (Throwable t) { - LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); + if (this.eap$SmartDoublingEnabled) { + // 智能翻倍启用时,确保输入框可见 + if (!this.renderables.contains(this.eap$PerProviderLimitInput)) { + this.addRenderableWidget(this.eap$PerProviderLimitInput); } + // 未聚焦且内容为空时,显示0 + if (!focused && this.eap$PerProviderLimitInput.getValue().trim().isEmpty()) { + this.eap$PerProviderLimitInput.setValue("0"); + } + + // 定位输入框到智能翻倍按钮左侧 + Button ref = eap$SmartDoublingToggle; + if (ref != null) { + int ex = ref.getX() - this.eap$PerProviderLimitInput.getWidth() - 5; + int ey = ref.getY() + 2; + this.eap$PerProviderLimitInput.setX(ex); + this.eap$PerProviderLimitInput.setY(ey); + } + + // 设置 tooltip + String cur = this.eap$PerProviderLimitInput.getValue(); + if (cur.isBlank()) cur = "0"; + this.eap$PerProviderLimitInput.setTooltip(Tooltip.create(Component.literal("单样物品发配数量上限: " + cur))); + } else { + // 智能翻倍未启用时,移除输入框 + this.removeWidget(this.eap$PerProviderLimitInput); } } } 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 index 2ae2419..50fa861 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java @@ -5,7 +5,7 @@ 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 com.extendedae_plus.api.IAdvancedBlocking; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.player.Player; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; @@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Collections; @Mixin(value = AdvPatternProviderLogic.class, remap = false) -public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder { +public class AdvPatternProviderLogicAdvancedMixin implements IAdvancedBlocking { @Unique private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking"; 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 index 5436f6f..3dbf49f 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java @@ -2,8 +2,8 @@ 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.api.smartDoubling.ISmartDoublingAwarePattern; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.player.Player; @@ -15,13 +15,15 @@ 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"; +import static com.extendedae_plus.util.smartDoubling.PatternScaler.getComputedMul; - @Unique - private boolean eap$smartDoubling = false; +@Mixin(value = AdvPatternProviderLogic.class, remap = false) +public class AdvPatternProviderLogicDoublingMixin implements ISmartDoublingHolder { + @Unique private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling"; + @Unique private static final String EAP_PROVIDER_SCALING_LIMIT = "eap_provider_scaling_limit"; + + @Unique private boolean eap$smartDoubling = false; + @Unique private int eap$providerScalingLimit = 0; // 供应器级别的上限,0 表示不限制 @Override public boolean eap$getSmartDoubling() { @@ -31,23 +33,30 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder @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) { - } + } catch (Throwable ignored) {} + } + + @Override + public int eap$getProviderSmartDoublingLimit() { + return this.eap$providerScalingLimit; + } + + @Override + public void eap$setProviderSmartDoublingLimit(int limit) { + this.eap$providerScalingLimit = limit; + try { + ((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); + // 保存供应器级别上限 + tag.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit); } @Inject(method = "readFromNBT", at = @At("TAIL")) @@ -55,6 +64,9 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder if (tag.contains(EAP_SMART_DOUBLING_KEY)) { this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY); } + if (tag.contains(EAP_PROVIDER_SCALING_LIMIT)) { + this.eap$providerScalingLimit = tag.getInt(EAP_PROVIDER_SCALING_LIMIT); + } } @Inject(method = "updatePatterns", at = @At("TAIL")) @@ -62,13 +74,14 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder try { var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); boolean allow = this.eap$smartDoubling; + int limit = this.eap$providerScalingLimit; for (IPatternDetails details : list) { - if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) { - aware.eap$setAllowScaling(allow); + if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern pattern) { + pattern.eap$setAllowScaling(allow); + pattern.eap$setMultiplierLimit(getComputedMul(proc, limit)); } } - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } @Shadow @@ -77,6 +90,7 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); + output.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit); } @Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL")) @@ -86,5 +100,9 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder // 持久化到 world this.saveChanges(); } + if (input.contains(EAP_PROVIDER_SCALING_LIMIT)) { + this.eap$providerScalingLimit = input.getInt(EAP_PROVIDER_SCALING_LIMIT); + 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 index 7cde360..4be69e0 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuAdvancedMixin.java @@ -2,8 +2,8 @@ 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 com.extendedae_plus.api.IAdvancedBlocking; +import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; @@ -18,14 +18,14 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(AdvPatternProviderMenu.class) -public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync { +public abstract class AdvPatternProviderMenuAdvancedMixin implements IPatternProviderMenuAdvancedSync { @Final @Shadow(remap = false) protected AdvPatternProviderLogic logic; // 选择一个未占用的 GUI 同步 id(AE2 已用到 7),这里使用 21 以避冲突 @Unique - @GuiSync(22) + @GuiSync(20) public boolean eap$AdvancedBlocking = false; @Inject(method = "broadcastChanges", at = @At("HEAD")) @@ -33,7 +33,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv // 避免@Shadow父类方法,改用公共API:AEBaseMenu#isClientSide() if (!((AEBaseMenu) (Object) this).isClientSide()) { var l = this.logic; - if (l instanceof AdvancedBlockingHolder holder) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } @@ -44,7 +44,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof AdvancedBlockingHolder holder) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } catch (Throwable ignored) {} @@ -55,7 +55,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv 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) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } catch (Throwable ignored) {} 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 index 8b2e4e9..704e0e8 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/menu/AdvPatternProviderMenuDoublingMixin.java @@ -2,8 +2,8 @@ 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 com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; @@ -18,21 +18,25 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(AdvPatternProviderMenu.class) -public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync { +public abstract class AdvPatternProviderMenuDoublingMixin implements IPatternProviderMenuDoublingSync { @Final @Shadow(remap = false) protected AdvPatternProviderLogic logic; @Unique - @GuiSync(23) + @GuiSync(21) public boolean eap$SmartDoubling = false; + @Unique + @GuiSync(22) + public int eap$PerProviderScalingLimit = 0; // 0 = no limit @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) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } } @@ -41,8 +45,9 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv private void eap$initSmartSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof SmartDoublingHolder holder) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } catch (Throwable ignored) {} } @@ -51,8 +56,9 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv 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) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } catch (Throwable ignored) {} } @@ -61,4 +67,9 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv public boolean eap$getSmartDoublingSynced() { return this.eap$SmartDoubling; } + + @Override + public int eap$getScalingLimit() { + return this.eap$PerProviderScalingLimit; + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java index 28030b2..c614778 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java @@ -1,14 +1,16 @@ package com.extendedae_plus.mixin.ae2; import appeng.crafting.pattern.AEProcessingPattern; -import com.extendedae_plus.api.SmartDoublingAwarePattern; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @Mixin(value = AEProcessingPattern.class, remap = false) -public class AEProcessingPatternMixin implements SmartDoublingAwarePattern { +public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern { @Unique private boolean eap$allowScaling = false; // 默认不允许缩放 + @Unique + private int eap$multiplierLimit = 0; // 模式级别的倍数上限,0 表示不限制 @Override public boolean eap$allowScaling() { @@ -19,4 +21,14 @@ public class AEProcessingPatternMixin implements SmartDoublingAwarePattern { public void eap$setAllowScaling(boolean allow) { this.eap$allowScaling = allow; } + + @Override + public int eap$getMultiplierLimit() { + return this.eap$multiplierLimit; + } + + @Override + public void eap$setMultiplierLimit(int limit) { + this.eap$multiplierLimit = Math.max(0, limit); + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java index ec1a5c8..7332895 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java @@ -1,36 +1,74 @@ package com.extendedae_plus.mixin.ae2; import appeng.blockentity.crafting.CraftingBlockEntity; +import appeng.blockentity.crafting.CraftingMonitorBlockEntity; import appeng.me.cluster.implementations.CraftingCPUCluster; +import appeng.me.helpers.MachineSource; +import org.spongepowered.asm.mixin.Final; 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.Constant; -import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; @Mixin(value = CraftingCPUCluster.class, remap = false, priority = 2000) public abstract class CraftingCPUClusterMixin { - // 1) 提升“单方块线程上限”的常量,避免抛出 IAE 的 IllegalArgumentException - @ModifyConstant( + + @Shadow(remap = false) + private MachineSource machineSrc; + + @Shadow(remap = false) + @Final + private List blockEntities; + + @Shadow(remap = false) + @Final + private List status; + + @Shadow(remap = false) + private long storage; + + @Shadow(remap = false) + private int accelerator; + + /** + * 完全替换 addBlockEntity 方法,移除 16 线程限制 + * 在方法开始时注入,检查线程数,如果超过 16 就手动处理并取消原方法 + */ + @Inject( method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", - constant = @Constant(intValue = 16) + at = @At("HEAD"), + cancellable = true, + require = 0 // 设置为 0,即使注入失败也不会崩溃 ) - private int extendedae_plus$raisePerUnitLimit(int original) { - // 放宽到极大值,完全取消单方块 16 线程的硬限制 - return Integer.MAX_VALUE; - } - - // 2) 保持统计使用原始线程值(若存在多处调用),不再返回固定 16 - @Redirect( - method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", - at = @At( - value = "INVOKE", - target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I", - ordinal = 1 - ) - ) - private int extendedae_plus$onGetThreadsForLimitCheck(CraftingBlockEntity te) { - // 返回原始线程数,确保总并行单元不被错误下限 - return te.getAcceleratorThreads(); + private void extendedae_plus$removeThreadLimit(CraftingBlockEntity te, CallbackInfo ci) { + // 只有当线程数超过 16 时才接管处理 + if (te.getAcceleratorThreads() > 16) { + // 手动实现 addBlockEntity 的逻辑,但不检查 16 的限制 + if (this.machineSrc == null || te.isCoreBlock()) { + this.machineSrc = new MachineSource(te); + } + + te.setCoreBlock(false); + te.saveChanges(); + this.blockEntities.add(0, te); + + if (te instanceof CraftingMonitorBlockEntity) { + this.status.add((CraftingMonitorBlockEntity) te); + } + if (te.getStorageBytes() > 0) { + this.storage += te.getStorageBytes(); + } + if (te.getAcceleratorThreads() > 0) { + // 这里不检查 <= 16 的限制,直接添加 + this.accelerator += te.getAcceleratorThreads(); + } + + // 取消原方法执行 + ci.cancel(); + } + // 如果 <= 16,让原方法正常执行 } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/AEBaseScreenInvoker.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/AEBaseScreenInvoker.java deleted file mode 100644 index 0ec30f9..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/AEBaseScreenInvoker.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.extendedae_plus.mixin.ae2.accessor; - -import appeng.client.gui.AEBaseScreen; -import appeng.menu.AEBaseMenu; -import org.spongepowered.asm.mixin.Mixin; - -@Mixin(AEBaseScreen.class) -public interface AEBaseScreenInvoker { - // 空接口:避免在 AEBaseScreen 上声明不存在方法的 Invoker 导致编译错误 -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicAccessor.java index 6155a13..eaf4672 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicAccessor.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicAccessor.java @@ -1,11 +1,14 @@ package com.extendedae_plus.mixin.ae2.accessor; +import appeng.api.crafting.IPatternDetails; import appeng.api.networking.IManagedGridNode; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import java.util.List; + @Mixin(PatternProviderLogic.class) public interface PatternProviderLogicAccessor { @Accessor(value = "host", remap = false) @@ -13,4 +16,7 @@ public interface PatternProviderLogicAccessor { @Accessor(value = "mainNode", remap = false) IManagedGridNode eap$mainNode(); + + @Accessor(value = "patterns" , remap = false) + List eap$patterns(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternInputsAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternInputsAccessor.java deleted file mode 100644 index 3f62fd4..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternInputsAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.extendedae_plus.mixin.ae2.accessor; - -import appeng.api.stacks.AEKey; -import appeng.helpers.patternprovider.PatternProviderLogic; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.Set; - -@Mixin(PatternProviderLogic.class) -public interface PatternProviderLogicPatternInputsAccessor { - @Accessor(value = "patternInputs",remap = false) - Set eap$patternInputs(); -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternsAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternsAccessor.java deleted file mode 100644 index c1471e3..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderLogicPatternsAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.extendedae_plus.mixin.ae2.accessor; - -import appeng.api.crafting.IPatternDetails; -import appeng.helpers.patternprovider.PatternProviderLogic; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.List; - -@Mixin(value = PatternProviderLogic.class, remap = false) -public interface PatternProviderLogicPatternsAccessor { - @Accessor("patterns") - List eap$patterns(); -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAdvancedAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAccessor.java similarity index 87% rename from src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAdvancedAccessor.java rename to src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAccessor.java index 18ca7b7..a1d296f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAdvancedAccessor.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/accessor/PatternProviderMenuAccessor.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(PatternProviderMenu.class) -public interface PatternProviderMenuAdvancedAccessor { +public interface PatternProviderMenuAccessor { @Accessor(value = "logic", remap = false) PatternProviderLogic eap$logic(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingServiceGetProvidersMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingServiceGetProvidersMixin.java index ccc4812..1a508c2 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingServiceGetProvidersMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingServiceGetProvidersMixin.java @@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.ae2.autopattern; import appeng.api.crafting.IPatternDetails; import appeng.me.service.CraftingService; -import com.extendedae_plus.content.ScaledProcessingPattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyArg; 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 1c4f00b..b326cd2 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 @@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.autopattern; import appeng.api.stacks.KeyCounter; import appeng.crafting.CraftingTreeNode; import appeng.crafting.inv.CraftingSimulationState; -import com.extendedae_plus.util.RequestedAmountHolder; +import com.extendedae_plus.util.smartDoubling.RequestedAmountHolder; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; 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 b8359ab..605caae 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 @@ -9,11 +9,11 @@ import appeng.crafting.CraftingTreeNode; import appeng.crafting.CraftingTreeProcess; import appeng.crafting.pattern.AEProcessingPattern; import appeng.me.service.CraftingService; -import com.extendedae_plus.api.SmartDoublingAwarePattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; import com.extendedae_plus.config.ModConfig; -import com.extendedae_plus.content.ScaledProcessingPattern; -import com.extendedae_plus.util.PatternScaler; -import com.extendedae_plus.util.RequestedAmountHolder; +import com.extendedae_plus.util.smartDoubling.PatternScaler; +import com.extendedae_plus.util.smartDoubling.RequestedAmountHolder; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; @@ -21,7 +21,7 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; import java.util.List; import java.util.stream.StreamSupport; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; /** * 注入 CraftingTreeProcess 构造器尾部:将 AEProcessingPattern 替换为 ScaledProcessingPattern @@ -36,20 +36,24 @@ public abstract class CraftingTreeProcessMixin { argsOnly = true, remap = false ) - private static IPatternDetails eap$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) { + private static IPatternDetails eap$replaceDetailsAtHead(IPatternDetails original, + ICraftingService cc, + CraftingCalculation job, + IPatternDetails details, + CraftingTreeNode craftingTreeNode) { try { // 若传入的 details 已经是缩放样板,且原始样板不允许缩放,则直接解包为原始样板 if (details instanceof ScaledProcessingPattern sp) { - var proc0 = sp.getOriginal(); - if (proc0 instanceof SmartDoublingAwarePattern aware0 && !aware0.eap$allowScaling()) { - return proc0; + var originalPattern = sp.getOriginal(); + if (originalPattern instanceof ISmartDoublingAwarePattern scalingAwarePattern && !scalingAwarePattern.eap$allowScaling()) { + return originalPattern; } } - if (!(details instanceof AEProcessingPattern proc)) return original; + if (!(details instanceof AEProcessingPattern processingPattern)) return original; // 若样板标记为不允许缩放,则直接跳过 - if (proc instanceof SmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { + if (processingPattern instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { return original; } @@ -89,10 +93,10 @@ public abstract class CraftingTreeProcessMixin { } // 使用每-provider 的分配量来缩放样板 - var scaled = PatternScaler.scale(proc, parentTarget, perProvider); + var scaled = PatternScaler.scale(processingPattern, parentTarget, perProvider); return scaled != null ? scaled : original; } catch (Exception e) { - LOGGER.warn("构建倍增样板出错", e); + EAP$LOGGER.warn("构建倍增样板出错", e); e.printStackTrace(); return original; } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java index d6dc2a5..a1b6bf7 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java @@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.ae2.autopattern; import appeng.api.crafting.IPatternDetails; import appeng.helpers.patternprovider.PatternProviderLogic; -import com.extendedae_plus.content.ScaledProcessingPattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java index f65d128..df7e08a 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java @@ -13,14 +13,14 @@ import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.Text; import appeng.client.gui.style.TextAlignment; import appeng.menu.slot.AppEngSlot; -import com.extendedae_plus.api.ExPatternPageAccessor; +import com.extendedae_plus.api.IExPatternPageAccessor; import com.extendedae_plus.content.ClientPatternHighlightStore; import com.extendedae_plus.init.ModNetwork; -import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket; -import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket; +import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor; +import com.extendedae_plus.network.crafting.CraftingMonitorJumpC2SPacket; +import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket; import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; -import com.mojang.logging.LogUtils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; @@ -40,17 +40,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(AEBaseScreen.class) public abstract class AEBaseScreenMixin { - @Unique - private ScreenStyle eap$getStyle(Object self) { - try { - var f = self.getClass().getDeclaredField("style"); - f.setAccessible(true); - Object v = f.get(self); - if (v instanceof ScreenStyle s) return s; - } catch (Throwable ignored) {} - return null; - } - /** * 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+左键, * 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket。 @@ -77,8 +66,7 @@ public abstract class AEBaseScreenMixin { } ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key)); cir.setReturnValue(true); - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } /** @@ -107,8 +95,7 @@ public abstract class AEBaseScreenMixin { } ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key)); cir.setReturnValue(true); - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } @Unique @@ -266,7 +253,7 @@ public abstract class AEBaseScreenMixin { int cur = 1; int max = 1; - if (self instanceof ExPatternPageAccessor accessor) { + if (self instanceof IExPatternPageAccessor accessor) { cur = Math.max(0, accessor.eap$getCurrentPage()) + 1; } try { @@ -281,7 +268,7 @@ public abstract class AEBaseScreenMixin { String pageText = "第" + cur + "页" + "/" + max + "页"; - ScreenStyle style = eap$getStyle(self); + ScreenStyle style = ((AEBaseScreenAccessor) this).eap$getStyle(); int color = 0xFFFFFFFF; if (style != null) { try { @@ -299,11 +286,9 @@ public abstract class AEBaseScreenMixin { guiGraphics.drawString(font, pageText, lineWidth + padding, 0, color, false); guiGraphics.pose().popPose(); } - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } - @Shadow(remap = false) protected void setTextContent(String id, Component content) {}; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/InterfaceScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/InterfaceScreenMixin.java index b03fabc..e998b5c 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/InterfaceScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/InterfaceScreenMixin.java @@ -3,17 +3,15 @@ package com.extendedae_plus.mixin.ae2.client.gui; import appeng.client.gui.AEBaseScreen; import appeng.client.gui.implementations.InterfaceScreen; import appeng.menu.AEBaseMenu; -import com.extendedae_plus.NewIcon; -import com.extendedae_plus.init.ModNetwork; -import com.extendedae_plus.network.InterfaceAdjustConfigAmountC2SPacket; import appeng.menu.SlotSemantics; -import net.minecraft.world.inventory.Slot; +import appeng.menu.implementations.InterfaceMenu; +import com.extendedae_plus.ae.client.gui.NewIcon; +import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor; import com.extendedae_plus.mixin.accessor.ScreenAccessor; +import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket; import com.glodblock.github.extendedae.client.button.ActionEPPButton; -import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.network.chat.Component; -import com.mojang.logging.LogUtils; +import net.minecraft.world.inventory.Slot; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -170,7 +168,7 @@ public abstract class InterfaceScreenMixin { // 获取菜单与配置槽列表 var screen = (AEBaseScreen) (Object) this; var menu = screen.getMenu(); - if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { + if (!(menu instanceof InterfaceMenu interfaceMenu)) { return; } var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); @@ -273,7 +271,7 @@ public abstract class InterfaceScreenMixin { } var screen = (AEBaseScreen) (Object) this; var menu = screen.getMenu(); - if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { + if (!(menu instanceof InterfaceMenu interfaceMenu)) { return; } var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternEncodingTermScreenMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternEncodingTermScreenMixin.java index 23c262f..eaa7a16 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternEncodingTermScreenMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternEncodingTermScreenMixin.java @@ -11,6 +11,7 @@ import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor; import com.extendedae_plus.mixin.accessor.ScreenAccessor; import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor; +import com.extendedae_plus.network.provider.RequestProvidersListC2SPacket; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.renderer.Rect2i; @@ -41,7 +42,7 @@ public abstract class PatternEncodingTermScreenMixin { // 复用已存在的按钮实例,避免重复创建 if (eap$uploadBtn == null) { eap$uploadBtn = new IconButton(btn -> ModNetwork.CHANNEL - .sendToServer(new com.extendedae_plus.network.RequestProvidersListC2SPacket())) { + .sendToServer(new RequestProvidersListC2SPacket())) { private final float eap$scale = 0.75f; // 约 12x12 @Override 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 4613b2c..4370433 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 @@ -16,7 +16,6 @@ public class PatternProviderCloseMixin { if (((Object) this) instanceof PatternProviderScreen) { ClientPatternHighlightStore.clearAll(); } - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } } \ No newline at end of file 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 863fab3..f670b38 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 @@ -1,19 +1,23 @@ package com.extendedae_plus.mixin.ae2.client.gui; -import appeng.api.config.Settings; import appeng.api.config.YesNo; import appeng.client.gui.AEBaseScreen; import appeng.client.gui.implementations.PatternProviderScreen; import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.widgets.SettingToggleButton; import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.api.ExPatternButtonsAccessor; -import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; -import com.extendedae_plus.api.PatternProviderMenuDoublingSync; +import com.extendedae_plus.api.IExPatternButtonsAccessor; +import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync; +import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync; import com.extendedae_plus.init.ModNetwork; -import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket; -import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket; +import com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket; +import com.extendedae_plus.network.provider.ToggleAdvancedBlockingC2SPacket; +import com.extendedae_plus.network.provider.ToggleSmartDoublingC2SPacket; +import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import org.spongepowered.asm.mixin.Mixin; @@ -22,7 +26,10 @@ 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; +import java.util.List; + +import static com.extendedae_plus.util.GuiUtil.createToggle; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; /** * 为 AE2 原版样板供应器界面添加“高级阻挡模式”按钮。 @@ -32,114 +39,174 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; @Mixin(PatternProviderScreen.class) public abstract class PatternProviderScreenMixin extends AEBaseScreen { - @Unique - private SettingToggleButton eap$AdvancedBlockingToggle; + // 高级阻挡模式切换按钮 + @Unique private SettingToggleButton eap$AdvancedBlockingToggle; + // 智能翻倍切换按钮 + @Unique private SettingToggleButton eap$SmartDoublingToggle; + // 智能翻倍上限输入框 + @Unique private EditBox eap$PerProviderLimitInput; - @Unique - private boolean eap$AdvancedBlockingEnabled = false; - - @Unique - private SettingToggleButton eap$SmartDoublingToggle; - - @Unique - private boolean eap$SmartDoublingEnabled = false; + // 当前高级阻挡模式是否启用 + @Unique private boolean eap$AdvancedBlockingEnabled = false; + // 当前智能翻倍是否启用 + @Unique private boolean eap$SmartDoublingEnabled = false; + // 当前智能翻倍上限 + @Unique private int eap$PerProviderScalingLimit = 0; public PatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { super(menu, playerInventory, title, style); } - @Inject(method = "", at = @At("RETURN")) - private void eap$initAdvancedBlocking(C menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { - // 使用 @GuiSync 初始化 + /** 同步服务端状态(初始化时调用) */ + @Unique + private void eap$syncInitialState(C menu) { try { - if (menu instanceof PatternProviderMenuAdvancedSync sync) { + // 同步高级阻挡和智能翻倍的服务端状态 + if (menu instanceof IPatternProviderMenuAdvancedSync 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 = net.minecraft.network.chat.Component.literal("智能阻挡"); - var line = enabled - ? net.minecraft.network.chat.Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)") - : net.minecraft.network.chat.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(); + if (menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit(); } } catch (Throwable t) { - LOGGER.error("Error initializing smart doubling sync", t); + EAP$LOGGER.error("Error initializing 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 = net.minecraft.network.chat.Component.literal("智能翻倍"); - var line = enabled - ? net.minecraft.network.chat.Component.literal("已启用:根据请求量对处理样板进行智能缩放") - : net.minecraft.network.chat.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)同步布尔值,保持按钮状态一致 + /** 创建并添加按钮和输入框 */ + @Unique + private void eap$createWidgets() { + // 高级阻挡 + this.eap$AdvancedBlockingToggle = createToggle( + eap$AdvancedBlockingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket()), + () -> { + var t = Component.literal("智能阻挡"); + var line = eap$AdvancedBlockingEnabled + ? Component.literal("已启用:对于同一种配方将不再阻挡 (需要启用原版阻挡模式)") + : Component.literal("已禁用:建议开启以获得更智能的阻挡行为"); + return List.of(t, line); + } + ); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$AdvancedBlockingToggle); + + // 智能翻倍 + this.eap$SmartDoublingToggle = createToggle( + eap$SmartDoublingEnabled, + () -> ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket()), + () -> { + var t = Component.literal("智能翻倍"); + var line = eap$SmartDoublingEnabled + ? Component.literal("已启用:根据请求量对处理样板进行智能缩放") + : Component.literal("已禁用:按原始样板数量进行发配"); + return List.of(t, line); + } + ); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + this.addToLeftToolbar(this.eap$SmartDoublingToggle); + + // 缩放上限输入框(使用 GuiUtil 抽离) + this.eap$PerProviderLimitInput = GuiUtil.createPerProviderLimitInput(this.font, this.eap$PerProviderScalingLimit, limit -> { + this.eap$PerProviderScalingLimit = limit; + ModNetwork.CHANNEL.sendToServer(new SetPerProviderScalingLimitC2SPacket(limit)); + }); + this.addRenderableWidget(this.eap$PerProviderLimitInput); + } + + /* ---------------------------- 注入点 ---------------------------- */ + @Inject(method = "", at = @At("RETURN")) + private void eap$onInit(C menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 初始化时同步服务端状态并创建控件 + eap$syncInitialState(menu); + eap$createWidgets(); + } + + /** 每帧刷新:从服务端同步状态并更新 UI */ @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); + private void eap$updateBeforeRender(CallbackInfo ci) { + // 每帧刷新按钮和输入框状态 + eap$updateToggles(); + eap$updateLimitInput(); + eap$updateExGuiLayout(); + } + + /* ---------------------------- 刷新逻辑 ---------------------------- */ + /** + * 刷新切换按钮的状态(与服务端同步) + */ + @Unique + private void eap$updateToggles() { + if (this.eap$AdvancedBlockingToggle != null && this.menu instanceof IPatternProviderMenuAdvancedSync sync) { + this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); + this.eap$AdvancedBlockingToggle.set(eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO); + } + if (this.eap$SmartDoublingToggle != null && this.menu instanceof IPatternProviderMenuDoublingSync sync) { + this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced(); + this.eap$SmartDoublingToggle.set(eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO); + } + } + + /** + * 刷新输入框的内容和可见性 + */ + @Unique + private void eap$updateLimitInput() { + if (this.eap$PerProviderLimitInput == null) return; + + int remoteLimit = this.eap$PerProviderScalingLimit; + // 获取服务端最新的 scaling limit + if (this.menu instanceof IPatternProviderMenuDoublingSync sync) { + remoteLimit = sync.eap$getScalingLimit(); } - 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); + boolean focused = this.eap$PerProviderLimitInput.isFocused(); + // 如果未聚焦且服务端有变化,则同步显示 + if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) { + this.eap$PerProviderScalingLimit = remoteLimit; + this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit)); } + if (this.eap$SmartDoublingEnabled) { + // 智能翻倍启用时,确保输入框可见 + if (!this.renderables.contains(this.eap$PerProviderLimitInput)) { + this.addRenderableWidget(this.eap$PerProviderLimitInput); + } + // 未聚焦且内容为空时,显示0 + if (!focused && this.eap$PerProviderLimitInput.getValue().trim().isEmpty()) { + this.eap$PerProviderLimitInput.setValue("0"); + } + + // 定位输入框到智能翻倍按钮左侧 + Button ref = eap$SmartDoublingToggle; + if (ref != null) { + int ex = ref.getX() - this.eap$PerProviderLimitInput.getWidth() - 5; + int ey = ref.getY() + 2; + this.eap$PerProviderLimitInput.setX(ex); + this.eap$PerProviderLimitInput.setY(ey); + } + + // 设置 tooltip + String cur = this.eap$PerProviderLimitInput.getValue(); + if (cur.isBlank()) cur = "0"; + this.eap$PerProviderLimitInput.setTooltip(Tooltip.create(Component.literal("单样物品发配数量上限: " + cur))); + } else { + // 智能翻倍未启用时,移除输入框 + this.removeWidget(this.eap$PerProviderLimitInput); + } + } + + /** + * 如果是扩展 GUI,刷新扩展按钮布局 + */ + @Unique + private void eap$updateExGuiLayout() { if ((Object) this instanceof GuiExPatternProvider) { try { - ((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); + ((IExPatternButtonsAccessor) this).eap$updateButtonsLayout(); } catch (Throwable t) { - LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); + EAP$LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java deleted file mode 100644 index d02b4f9..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.extendedae_plus.mixin.ae2.client.gui; - -import appeng.api.upgrades.Upgrades; -import appeng.client.gui.AEBaseScreen; -import appeng.client.gui.implementations.PatternProviderScreen; -import appeng.client.gui.style.ScreenStyle; -import appeng.client.gui.widgets.ToolboxPanel; -import appeng.client.gui.widgets.UpgradesPanel; -import appeng.core.localization.GuiText; -import appeng.menu.SlotSemantics; -import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.bridge.IUpgradableMenu; -import com.extendedae_plus.compat.UpgradeSlotCompat; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.player.Inventory; -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 java.util.ArrayList; -import java.util.List; - -@Mixin(value = PatternProviderScreen.class, priority = 2000, remap = false) -public abstract class PatternProviderScreenUpgradesMixin extends AEBaseScreen { - - @Inject(method = "", at = @At("TAIL")) - private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { - // 只有在应该启用升级卡槽时才添加升级面板 - if (!UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) { - return; - } - - this.widgets.add("upgrades", new UpgradesPanel( - menu.getSlots(SlotSemantics.UPGRADE), - this::eap$getCompatibleUpgrades)); - if (((IUpgradableMenu) menu).getToolbox() != null && ((IUpgradableMenu) menu).getToolbox().isPresent()) { - this.widgets.add("toolbox", new ToolboxPanel(style, ((IUpgradableMenu) menu).getToolbox().getName())); - } - } - - @Unique - private List eap$getCompatibleUpgrades() { - var list = new ArrayList(); - list.add(GuiText.CompatibleUpgrades.text()); - list.addAll(Upgrades.getTooltipLinesForMachine(((IUpgradableMenu) menu).getUpgrades().getUpgradableItem())); - return list; - } - - public PatternProviderScreenUpgradesMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { - super(menu, playerInventory, title, style); - } -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/SlotGridLayoutMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/SlotGridLayoutMixin.java index aa2267c..b40d099 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/SlotGridLayoutMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/SlotGridLayoutMixin.java @@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.ae2.client.gui; import appeng.client.Point; import appeng.client.gui.layout.SlotGridLayout; +import com.extendedae_plus.api.IExPatternPageAccessor; import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -32,7 +33,7 @@ public abstract class SlotGridLayoutMixin { // 读取实际当前页码:优先从 GUI accessor,其次反射容器,失败则为 0 int currentPage = 0; try { - if (screen instanceof com.extendedae_plus.api.ExPatternPageAccessor accessor) { + if (screen instanceof IExPatternPageAccessor accessor) { currentPage = accessor.eap$getCurrentPage(); } else { var menu = ((com.glodblock.github.extendedae.client.gui.GuiExPatternProvider) screen).getMenu(); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java index edba48a..db7ef04 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java @@ -6,6 +6,7 @@ import appeng.menu.AEBaseMenu; import appeng.menu.ToolboxMenu; import appeng.menu.implementations.PatternProviderMenu; import com.extendedae_plus.compat.UpgradeSlotCompat; +import com.extendedae_plus.util.Logger; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import org.spongepowered.asm.mixin.Mixin; @@ -43,7 +44,7 @@ public abstract class PatternProviderCompatMixin extends AEBaseMenu implements U } } catch (Exception e) { // 静默处理异常,确保不会因为升级功能导致崩溃 - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e); + Logger.EAP$LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java index 6017f35..0c22e53 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java @@ -8,11 +8,12 @@ import appeng.api.upgrades.UpgradeInventories; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import com.extendedae_plus.ae.items.ChannelCardItem; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.ae.wireless.WirelessSlaveLink; +import com.extendedae_plus.ae.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import com.extendedae_plus.compat.UpgradeSlotCompat; import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.wireless.WirelessSlaveLink; -import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.util.Logger; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Final; @@ -31,7 +32,7 @@ import java.util.List; * 根据appflux是否存在来决定是否实现IUpgradeableObject接口 */ @Mixin(value = PatternProviderLogic.class, priority = 500, remap = false) -public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { +public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, IInterfaceWirelessLinkBridge { @Unique private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty(); @@ -75,7 +76,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj eap$compatInitializeChannelLink(); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级变更处理失败", e); + Logger.EAP$LOGGER.error("兼容性升级变更处理失败", e); } } @@ -90,7 +91,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj eap$compatInitializeChannelLink(); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("监听appflux升级变化失败", e); + Logger.EAP$LOGGER.error("监听appflux升级变化失败", e); } } @@ -98,11 +99,11 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj at = @At("TAIL")) private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { try { - + boolean upgradeSlots = UpgradeSlotCompat.shouldEnableUpgradeSlots(); boolean channelCard = UpgradeSlotCompat.shouldEnableChannelCard(); - + if (upgradeSlots) { // 只有在升级槽功能启用时才创建升级槽 this.eap$compatUpgrades = UpgradeInventories.forMachine( @@ -115,7 +116,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } else { } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级初始化失败", e); + Logger.EAP$LOGGER.error("兼容性升级初始化失败", e); } } @@ -126,7 +127,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades"); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级保存失败", e); + Logger.EAP$LOGGER.error("兼容性升级保存失败", e); } } @@ -143,7 +144,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级加载失败", e); + Logger.EAP$LOGGER.error("兼容性升级加载失败", e); } } @@ -158,7 +159,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级掉落失败", e); + Logger.EAP$LOGGER.error("兼容性升级掉落失败", e); } } @@ -169,7 +170,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj this.eap$compatUpgrades.clear(); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级清理失败", e); + Logger.EAP$LOGGER.error("兼容性升级清理失败", e); } } @@ -181,7 +182,6 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } else { // 装了appflux时,这个方法不应该被调用,因为appflux的Mixin会覆盖它 // 但是为了安全起见,返回空的升级槽 - com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("装了appflux时getUpgrades被调用,这不应该发生"); return UpgradeInventories.empty(); } } @@ -197,7 +197,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj eap$compatLink.updateStatus(); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性无线链接更新失败", e); + Logger.EAP$LOGGER.error("兼容性无线链接更新失败", e); } } @@ -243,10 +243,9 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj if (this instanceof IUpgradeableObject) { IUpgradeableObject upgradeableThis = (IUpgradeableObject) this; upgrades = upgradeableThis.getUpgrades(); - com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("从appflux获取到升级槽: {}", upgrades != null); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取appflux升级槽失败", e); + Logger.EAP$LOGGER.error("获取appflux升级槽失败", e); } } @@ -254,6 +253,11 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj for (ItemStack stack : upgrades) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { channel = ChannelCardItem.getChannel(stack); + java.util.UUID ownerUUID = ChannelCardItem.getOwnerUUID(stack); + if (ownerUUID != null) { + // 保存ownerUUID到局部变量,后面设置到link + channel |= ((long) ownerUUID.hashCode() << 32); // 临时存储 + } found = true; break; } @@ -275,6 +279,18 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj eap$compatLink = new WirelessSlaveLink(endpoint); } + // 从频道卡重新读取ownerUUID并设置 + java.util.UUID cardOwner = null; + if (upgrades != null) { + for (ItemStack stack : upgrades) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + cardOwner = ChannelCardItem.getOwnerUUID(stack); + channel = ChannelCardItem.getChannel(stack); // 重新读取正确的频率 + break; + } + } + } + eap$compatLink.setPlacerId(cardOwner); eap$compatLink.setFrequency(channel); eap$compatLink.updateStatus(); @@ -290,7 +306,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } catch (Throwable ignored) {} } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性频道链接初始化失败", e); + Logger.EAP$LOGGER.error("兼容性频道链接初始化失败", e); } } @@ -314,7 +330,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj return eap$compatLink != null && eap$compatLink.isConnected(); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("检查兼容性无线连接状态失败", e); + Logger.EAP$LOGGER.error("检查兼容性无线连接状态失败", e); return false; } } @@ -363,7 +379,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj } } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性延迟初始化失败", e); + Logger.EAP$LOGGER.error("兼容性延迟初始化失败", e); } } @@ -383,7 +399,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj }); } catch (Throwable ignored) {} } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性主节点状态变更处理失败", e); + Logger.EAP$LOGGER.error("兼容性主节点状态变更处理失败", e); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java index eaee82c..32b450f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java @@ -10,6 +10,7 @@ import appeng.core.localization.GuiText; import appeng.menu.SlotSemantics; import appeng.menu.implementations.PatternProviderMenu; import com.extendedae_plus.compat.UpgradeSlotCompat; +import com.extendedae_plus.util.Logger; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import org.spongepowered.asm.mixin.Mixin; @@ -38,7 +39,7 @@ public abstract class PatternProviderScreenCompatMixin cir) { - if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) { bridge.eap$updateWirelessLink(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicUpgradesMixin.java similarity index 98% rename from src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java rename to src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicUpgradesMixin.java index cdecdfa..64fc667 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicUpgradesMixin.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.ae2; +package com.extendedae_plus.mixin.ae2.helpers; import appeng.api.networking.IManagedGridNode; import appeng.api.upgrades.IUpgradeInventory; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicAdvancedMixin.java similarity index 95% rename from src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java rename to src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicAdvancedMixin.java index 31b4654..157d81f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicAdvancedMixin.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.ae2.helpers; +package com.extendedae_plus.mixin.ae2.helpers.patternprovider; import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails.IInput; @@ -6,7 +6,7 @@ import appeng.api.stacks.AEKey; import appeng.api.stacks.GenericStack; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderTarget; -import com.extendedae_plus.api.AdvancedBlockingHolder; +import com.extendedae_plus.api.IAdvancedBlocking; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.player.Player; import org.spongepowered.asm.mixin.Mixin; @@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Collections; @Mixin(value = PatternProviderLogic.class, remap = false) -public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder { +public class PatternProviderLogicAdvancedMixin implements IAdvancedBlocking { @Unique private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking"; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicDoublingMixin.java similarity index 55% rename from src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java rename to src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicDoublingMixin.java index 13806f2..738252d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicDoublingMixin.java @@ -1,11 +1,11 @@ -package com.extendedae_plus.mixin.ae2.helpers; +package com.extendedae_plus.mixin.ae2.helpers.patternprovider; import appeng.api.crafting.IPatternDetails; import appeng.crafting.pattern.AEProcessingPattern; import appeng.helpers.patternprovider.PatternProviderLogic; -import com.extendedae_plus.api.SmartDoublingAwarePattern; -import com.extendedae_plus.api.SmartDoublingHolder; -import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicPatternsAccessor; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.entity.player.Player; import org.spongepowered.asm.mixin.Mixin; @@ -15,13 +15,15 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(value = PatternProviderLogic.class, remap = false) -public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { - @Unique - private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling"; +import static com.extendedae_plus.util.smartDoubling.PatternScaler.getComputedMul; - @Unique - private boolean eap$smartDoubling = false; +@Mixin(value = PatternProviderLogic.class, remap = false) +public class PatternProviderLogicDoublingMixin implements ISmartDoublingHolder { + @Unique private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling"; + @Unique private static final String EAP_PROVIDER_SCALING_LIMIT = "eap_provider_scaling_limit"; + + @Unique private boolean eap$smartDoubling = false; + @Unique private int eap$providerScalingLimit = 0; // 供应器级别的上限,0 表示不限制 @Override public boolean eap$getSmartDoubling() { @@ -31,23 +33,31 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { @Override public void eap$setSmartDoubling(boolean value) { this.eap$smartDoubling = value; - // 立即将开关状态应用到当前 Provider 的样板上,避免等待下一次 updatePatterns + // 触发一次刷新 try { - var list = ((PatternProviderLogicPatternsAccessor) this).eap$patterns(); - for (IPatternDetails details : list) { - if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) { - aware.eap$setAllowScaling(value); - } - } - // 触发一次刷新,让网络及时拿到最新状态(也会触发 ICraftingProvider.requestUpdate(mainNode)) ((PatternProviderLogic) (Object) this).updatePatterns(); - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} + } + + @Override + public int eap$getProviderSmartDoublingLimit() { + return this.eap$providerScalingLimit; + } + + @Override + public void eap$setProviderSmartDoublingLimit(int limit) { + this.eap$providerScalingLimit = limit; + try { + ((PatternProviderLogic) (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); + // 保存供应器级别上限 + tag.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit); + } @Inject(method = "readFromNBT", at = @At("TAIL")) @@ -55,20 +65,24 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { if (tag.contains(EAP_SMART_DOUBLING_KEY)) { this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY); } + if (tag.contains(EAP_PROVIDER_SCALING_LIMIT)) { + this.eap$providerScalingLimit = tag.getInt(EAP_PROVIDER_SCALING_LIMIT); + } } @Inject(method = "updatePatterns", at = @At("TAIL")) private void eap$applySmartDoublingToPatterns(CallbackInfo ci) { try { - var list = ((PatternProviderLogicPatternsAccessor) this).eap$patterns(); + var list = ((PatternProviderLogicAccessor) this).eap$patterns(); boolean allow = this.eap$smartDoubling; + int limit = this.eap$providerScalingLimit; for (IPatternDetails details : list) { - if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) { - aware.eap$setAllowScaling(allow); + if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern pattern) { + pattern.eap$setAllowScaling(allow); + pattern.eap$setMultiplierLimit(getComputedMul(proc, limit)); } } - } catch (Throwable ignored) { - } + } catch (Throwable ignored) {} } @Shadow @@ -77,6 +91,7 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); + output.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit); } @Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL")) @@ -86,5 +101,9 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { // 持久化到 world this.saveChanges(); } + if (input.contains(EAP_PROVIDER_SCALING_LIMIT)) { + this.eap$providerScalingLimit = input.getInt(EAP_PROVIDER_SCALING_LIMIT); + this.saveChanges(); + } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java index e799173..27ecb7f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java @@ -1,7 +1,7 @@ package com.extendedae_plus.mixin.ae2.helpers.patternprovider; import appeng.helpers.patternprovider.PatternProviderLogic; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -27,7 +27,7 @@ public abstract class PatternProviderLogicTickerMixin { if (node != null && node.getLevel() != null && node.getLevel().isClientSide) { return; } - if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) { bridge.eap$handleDelayedInit(); } } @@ -35,7 +35,7 @@ public abstract class PatternProviderLogicTickerMixin { @Inject(method = "tickingRequest", at = @At("TAIL")) private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { - if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) { bridge.eap$updateWirelessLink(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/items/MemoryCardItemMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/items/MemoryCardItemMixin.java new file mode 100644 index 0000000..7add032 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/items/MemoryCardItemMixin.java @@ -0,0 +1,223 @@ +package com.extendedae_plus.mixin.ae2.items; + +import appeng.api.inventories.InternalInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.core.localization.PlayerMessages; +import appeng.items.tools.MemoryCardItem; +import appeng.items.tools.NetworkToolItem; +import appeng.util.inv.PlayerInternalInventory; +import com.extendedae_plus.ae.parts.EntitySpeedTickerPart; +import com.extendedae_plus.init.ModItems; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.spongepowered.asm.mixin.Mixin; +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 java.util.ArrayList; + +@Mixin(value = MemoryCardItem.class, remap = false) +public class MemoryCardItemMixin { + + /** + * 写入 Memory Card 时保留实体加速卡的完整 NBT 数据 + */ + @Inject(method = "storeUpgrades", at = @At("HEAD"), cancellable = true) + private static void storeUpgradesCustom(IUpgradeableObject upgradeableObject, CompoundTag output, CallbackInfo ci) { + try { + CompoundTag desiredUpgradesTag = new CompoundTag(); + ListTag entitySpeedCards = new ListTag(); + InternalInventory upgrades = upgradeableObject.getUpgrades(); + + for (int i = 0; i < upgrades.size(); i++) { + ItemStack upgradeStack = upgrades.getStackInSlot(i); + if (upgradeStack.isEmpty()) continue; + + ResourceLocation itemId = BuiltInRegistries.ITEM.getKey(upgradeStack.getItem()); + String key = itemId.toString(); + + if (upgradeStack.getItem().equals(ModItems.ENTITY_SPEED_CARD.get())) { + CompoundTag stackTag = new CompoundTag(); + stackTag.putInt("Slot", i); + upgradeStack.save(stackTag); + entitySpeedCards.add(stackTag); + } else { + desiredUpgradesTag.putInt(key, desiredUpgradesTag.getInt(key) + upgradeStack.getCount()); + } + } + + if (!entitySpeedCards.isEmpty()) { + desiredUpgradesTag.put("entity_speed_cards", entitySpeedCards); + } + + output.put("upgrades", desiredUpgradesTag); + ci.cancel(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 从 Memory Card 恢复升级时,从玩家背包或网络工具提取实体加速卡 + */ + @Inject(method = "restoreUpgrades", at = @At("HEAD"), cancellable = true) + private static void restoreUpgradesCustom(Player player, CompoundTag input, IUpgradeableObject upgradeableObject, CallbackInfoReturnable cir) { + try { + if (!input.contains("upgrades")) { + cir.setReturnValue(false); + return; + } + + CompoundTag desiredUpgradesTag = input.getCompound("upgrades"); + InternalInventory upgrades = upgradeableObject.getUpgrades(); + + // 收集背包和网络工具作为升级卡来源 + var upgradeSources = new ArrayList(); + upgradeSources.add(new PlayerInternalInventory(player.getInventory())); + var networkTool = NetworkToolItem.findNetworkToolInv(player); + if (networkTool != null) { + upgradeSources.add(networkTool.getInventory()); + } + + // 清空所有槽位中的 EntitySpeedCardItem + for (int i = 0; i < upgrades.size(); i++) { + ItemStack stack = upgrades.getStackInSlot(i); + if (!stack.isEmpty() && stack.getItem().equals(ModItems.ENTITY_SPEED_CARD.get())) { + ItemStack removed = upgrades.extractItem(i, stack.getCount(), false); + for (var source : upgradeSources) { + if (!removed.isEmpty()) { + removed = source.addItems(removed); + } + } + if (!removed.isEmpty()) { + player.drop(removed, false); + } + } + } + + // 恢复 EntitySpeedCardItem + if (desiredUpgradesTag.contains("entity_speed_cards", Tag.TAG_LIST)) { + ListTag entitySpeedCards = desiredUpgradesTag.getList("entity_speed_cards", Tag.TAG_COMPOUND); + for (int i = 0; i < entitySpeedCards.size(); i++) { + CompoundTag stackTag = entitySpeedCards.getCompound(i); + ItemStack desiredStack = ItemStack.of(stackTag); + int slot = stackTag.contains("Slot") ? stackTag.getInt("Slot") : i; + + if (player.getAbilities().instabuild) { + // 创造模式:直接生成 + if (slot >= 0 && slot < upgrades.size()) { + upgrades.setItemDirect(slot, desiredStack); + } else { + upgrades.addItems(desiredStack); + } + } else { + // 非创造模式:从背包或网络工具提取 + int missingAmount = desiredStack.getCount(); + ItemStack extracted = ItemStack.EMPTY; + + for (var source : upgradeSources) { + ItemStack potential = new ItemStack(desiredStack.getItem(), missingAmount); + if (desiredStack.hasTag()) { + potential.setTag(desiredStack.getTag().copy()); + } + ItemStack cards = source.removeItems(missingAmount, potential, null); + if (!cards.isEmpty()) { + ItemStack overflow = upgrades.addItems(cards); + if (!overflow.isEmpty()) { + player.getInventory().placeItemBackInInventory(overflow); + } + missingAmount -= cards.getCount(); + extracted = cards; + } + if (missingAmount <= 0) break; + } + + if (missingAmount > 0 && !player.level().isClientSide()) { + player.displayClientMessage( + PlayerMessages.MissingUpgrades.text(desiredStack.getItem().getDescription(), missingAmount), + true + ); + } else if (!extracted.isEmpty()) { + if (slot >= 0 && slot < upgrades.size()) { + upgrades.setItemDirect(slot, extracted); + } else { + upgrades.addItems(extracted); + } + } + } + } + } + + // 恢复其他升级卡(按 AE2 原逻辑) + for (String key : desiredUpgradesTag.getAllKeys()) { + ResourceLocation id; + try { + id = new ResourceLocation(key); + } catch (Exception ex) { + continue; + } + + var item = BuiltInRegistries.ITEM.getOptional(id).orElse(null); + if (item == null || item.equals(ModItems.ENTITY_SPEED_CARD.get())) continue; + + int desiredCount = desiredUpgradesTag.getInt(key); + if (desiredCount > 0) { + if (player.getAbilities().instabuild) { + // 创造模式:直接生成 + ItemStack stack = new ItemStack(item, desiredCount); + upgrades.addItems(stack); + } else { + // 非创造模式:从背包或网络工具提取 + int missingAmount = desiredCount; + ItemStack potential = new ItemStack(item, missingAmount); + ItemStack overflow = upgrades.addItems(potential, true); + if (!overflow.isEmpty()) { + missingAmount -= overflow.getCount(); + } + + for (var source : upgradeSources) { + ItemStack cards = source.removeItems(missingAmount, potential, null); + if (!cards.isEmpty()) { + overflow = upgrades.addItems(cards); + if (!overflow.isEmpty()) { + player.getInventory().placeItemBackInInventory(overflow); + } + missingAmount -= cards.getCount(); + } + if (missingAmount <= 0) break; + } + + if (missingAmount > 0 && !player.level().isClientSide()) { + player.displayClientMessage( + PlayerMessages.MissingUpgrades.text(item.getDescription(), missingAmount), + true + ); + } + } + } + } + + // 标记保存并通知升级变化 + if (upgradeableObject instanceof EntitySpeedTickerPart speedTickerPart) { + BlockEntity be = speedTickerPart.getBlockEntity(); + if (be != null) { + be.setChanged(); + } + speedTickerPart.upgradesChanged(); + } + + cir.setReturnValue(true); + } catch (Exception e) { + e.printStackTrace(); + cir.setReturnValue(false); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/items/QuartzCuttingKnifeItemMixin.java similarity index 97% rename from src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java rename to src/main/java/com/extendedae_plus/mixin/ae2/items/QuartzCuttingKnifeItemMixin.java index 420a3e6..31de43d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/QuartzCuttingKnifeItemMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/items/QuartzCuttingKnifeItemMixin.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.mixin.ae2; +package com.extendedae_plus.mixin.ae2.items; import appeng.api.parts.IPartHost; import appeng.api.parts.SelectedPart; @@ -36,7 +36,7 @@ import java.lang.reflect.Method; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; /** * 为 AE2 石英切割刀添加:潜行 + 右键 指向世界中的方块/部件(如线缆)时,复制其名称到剪贴板。 @@ -198,7 +198,7 @@ public abstract class QuartzCuttingKnifeItemMixin { } catch (ClassNotFoundException e) { return null; // GTCEu 不可用 } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) { - LOGGER.error("处理 GTCEu 配方翻译失败: {}", e.getMessage()); + EAP$LOGGER.error("处理 GTCEu 配方翻译失败: {}", e.getMessage()); return null; // 反射失败 } return null; // 非 GTCEu 方块实体 @@ -247,7 +247,7 @@ public abstract class QuartzCuttingKnifeItemMixin { try { latch.await(); } catch (InterruptedException e) { - LOGGER.error("剪贴板复制线程中断: {}", e.getMessage()); + EAP$LOGGER.error("剪贴板复制线程中断: {}", e.getMessage()); } return result.get(); } else { 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 28174ac..0ff4559 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 @@ -5,8 +5,8 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.menu.AEBaseMenu; import appeng.menu.guisync.GuiSync; import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.api.AdvancedBlockingHolder; -import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; +import com.extendedae_plus.api.IAdvancedBlocking; +import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import org.spongepowered.asm.mixin.Final; @@ -17,10 +17,10 @@ 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; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; @Mixin(PatternProviderMenu.class) -public abstract class PatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync { +public abstract class PatternProviderMenuAdvancedMixin implements IPatternProviderMenuAdvancedSync { @Final @Shadow(remap = false) protected PatternProviderLogic logic; @@ -35,7 +35,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide // 避免@Shadow父类方法,改用公共API:AEBaseMenu#isClientSide() if (!((AEBaseMenu) (Object) this).isClientSide()) { var l = this.logic; - if (l instanceof AdvancedBlockingHolder holder) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } @@ -46,7 +46,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof AdvancedBlockingHolder holder) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } catch (Throwable ignored) {} @@ -57,11 +57,11 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide private void eap$initAdvancedSync_Protected(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof AdvancedBlockingHolder holder) { + if (l instanceof IAdvancedBlocking holder) { this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); } } catch (Throwable t) { - LOGGER.error("Error initializing advanced sync", t); + EAP$LOGGER.error("Error initializing advanced sync", t); } } 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 3403b9d..caf1479 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 @@ -5,8 +5,8 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.menu.AEBaseMenu; import appeng.menu.guisync.GuiSync; import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.api.PatternProviderMenuDoublingSync; -import com.extendedae_plus.api.SmartDoublingHolder; +import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import org.spongepowered.asm.mixin.Final; @@ -17,10 +17,10 @@ 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; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; @Mixin(PatternProviderMenu.class) -public abstract class PatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync { +public abstract class PatternProviderMenuDoublingMixin implements IPatternProviderMenuDoublingSync { @Final @Shadow(remap = false) protected PatternProviderLogic logic; @@ -28,13 +28,17 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide @Unique @GuiSync(21) public boolean eap$SmartDoubling = false; + @Unique + @GuiSync(22) + public int eap$PerProviderScalingLimit = 0; // 0 = no limit @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) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } } @@ -43,11 +47,12 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide private void eap$initSmartSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof SmartDoublingHolder holder) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } catch (Throwable t) { - LOGGER.error("Error initializing smart doubling sync", t); + EAP$LOGGER.error("Error initializing smart doubling sync", t); } } @@ -55,16 +60,23 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide private void eap$initSmartSync_Protected(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { try { var l = this.logic; - if (l instanceof SmartDoublingHolder holder) { + if (l instanceof ISmartDoublingHolder holder) { this.eap$SmartDoubling = holder.eap$getSmartDoubling(); + this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit(); } } catch (Throwable t) { - LOGGER.error("Error initializing smart doubling sync", t); + EAP$LOGGER.error("Error initializing smart doubling sync", t); } } + @Override public boolean eap$getSmartDoublingSynced() { return this.eap$SmartDoubling; } + + @Override + public int eap$getScalingLimit() { + return this.eap$PerProviderScalingLimit; + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java index c5ed6d7..01ef29c 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java @@ -7,7 +7,7 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.menu.AEBaseMenu; import appeng.menu.ToolboxMenu; import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.bridge.IUpgradableMenu; +import com.extendedae_plus.api.bridge.IUpgradableMenu; import com.extendedae_plus.compat.UpgradeSlotCompat; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java index b180e30..10b957b 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java @@ -1,7 +1,7 @@ package com.extendedae_plus.mixin.ae2.parts; import appeng.parts.AEBasePart; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import net.minecraft.network.FriendlyByteBuf; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -18,8 +18,8 @@ public class AEBasePartClientSyncMixin { @Inject(method = "writeToStream", at = @At("TAIL")) private void eap$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) { // 检查是否实现了无线链接桥接接口 - if (this instanceof InterfaceWirelessLinkBridge) { - InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; + if (this instanceof IInterfaceWirelessLinkBridge) { + IInterfaceWirelessLinkBridge bridge = (IInterfaceWirelessLinkBridge) this; // 同步无线连接状态到客户端 boolean connected = false; try { @@ -44,8 +44,8 @@ public class AEBasePartClientSyncMixin { boolean connected = data.readBoolean(); // 检查是否实现了无线链接桥接接口 - if (this instanceof InterfaceWirelessLinkBridge) { - InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; + if (this instanceof IInterfaceWirelessLinkBridge) { + IInterfaceWirelessLinkBridge bridge = (IInterfaceWirelessLinkBridge) this; try { // 更新客户端状态 bridge.eap$setClientWirelessState(connected); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java index b00c3e1..a4048e0 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java @@ -3,16 +3,14 @@ package com.extendedae_plus.mixin.ae2.parts.automation; import appeng.api.networking.security.IActionHost; import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeableObject; -import appeng.helpers.InterfaceLogicHost; import appeng.parts.automation.IOBusPart; -import net.minecraft.nbt.CompoundTag; -import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.ae.items.ChannelCardItem; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.ae.wireless.WirelessSlaveLink; +import com.extendedae_plus.ae.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.wireless.WirelessSlaveLink; -import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; -import net.minecraft.network.FriendlyByteBuf; +import com.extendedae_plus.util.Logger; +import net.minecraft.nbt.CompoundTag; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -23,7 +21,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; * 给 AE2 的 I/O 总线注入频道卡联动:在升级变更时读取频道并更新无线链接。 */ @Mixin(value = IOBusPart.class, remap = false) -public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { +public abstract class IOBusPartChannelCardMixin implements IInterfaceWirelessLinkBridge, IUpgradeableObject { @Unique private WirelessSlaveLink eap$link; @@ -73,10 +71,12 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink try { IUpgradeInventory inv = this.getUpgrades(); long channel = 0L; + java.util.UUID ownerUUID = null; boolean found = false; for (var stack : inv) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { channel = ChannelCardItem.getChannel(stack); + ownerUUID = ChannelCardItem.getOwnerUUID(stack); found = true; break; } @@ -88,14 +88,14 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink } eap$lastChannel = channel; - ExtendedAELogger.LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel); + Logger.EAP$LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel); if (!found) { // 无频道卡则断开 if (eap$link != null) { eap$link.setFrequency(0L); eap$link.updateStatus(); - ExtendedAELogger.LOGGER.debug("[服务端] IOBus 断开频道链接"); + Logger.EAP$LOGGER.debug("[服务端] IOBus 断开频道链接"); // 立即通知客户端状态变化(断开连接无需延迟) ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); } @@ -108,17 +108,19 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink () -> ((IActionHost)(Object)this).getActionableNode() ); eap$link = new WirelessSlaveLink(endpoint); - ExtendedAELogger.LOGGER.debug("[服务端] IOBus 创建新的无线链接"); + Logger.EAP$LOGGER.debug("[服务端] IOBus 创建新的无线链接"); } + // 设置频道卡的所有者UUID(如果有的话) + eap$link.setPlacerId(ownerUUID); eap$link.setFrequency(channel); eap$link.updateStatus(); - ExtendedAELogger.LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); + Logger.EAP$LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); } catch (Exception e) { - ExtendedAELogger.LOGGER.error("[服务端] IOBus 初始化频道链接失败", e); + Logger.EAP$LOGGER.error("[服务端] IOBus 初始化频道链接失败", e); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java index d9ac8ba..a964e14 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java @@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.parts.automation; import appeng.api.networking.IGridNode; import appeng.api.networking.ticking.TickRateModulation; import appeng.parts.automation.IOBusPart; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -17,7 +17,7 @@ public abstract class IOBusPartTickerChannelCardMixin { @Inject(method = "tickingRequest", at = @At("TAIL")) private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { - if (this instanceof InterfaceWirelessLinkBridge bridge) { + if (this instanceof IInterfaceWirelessLinkBridge bridge) { bridge.eap$updateWirelessLink(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java index 50e4a71..63509b2 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java @@ -5,14 +5,13 @@ import appeng.api.networking.security.IActionHost; import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeableObject; import appeng.parts.storagebus.StorageBusPart; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.ae.items.ChannelCardItem; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.ae.wireless.WirelessSlaveLink; +import com.extendedae_plus.ae.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.wireless.WirelessSlaveLink; -import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.util.Logger; +import net.minecraft.nbt.CompoundTag; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -23,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; * 给 AE2 的存储总线注入频道卡联动:在升级变更时读取频道并更新无线链接。 */ @Mixin(value = StorageBusPart.class, remap = false) -public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { +public abstract class StorageBusPartChannelCardMixin implements IInterfaceWirelessLinkBridge, IUpgradeableObject { @Unique private WirelessSlaveLink eap$link; @@ -70,10 +69,12 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles try { IUpgradeInventory inv = this.getUpgrades(); long channel = 0L; + java.util.UUID ownerUUID = null; boolean found = false; for (var stack : inv) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { channel = ChannelCardItem.getChannel(stack); + ownerUUID = ChannelCardItem.getOwnerUUID(stack); found = true; break; } @@ -85,13 +86,13 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles } eap$lastChannel = channel; - ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel); + Logger.EAP$LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel); if (!found) { if (eap$link != null) { eap$link.setFrequency(0L); eap$link.updateStatus(); - ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 断开频道链接"); + Logger.EAP$LOGGER.debug("[服务端] StorageBus 断开频道链接"); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); } @@ -104,17 +105,19 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles () -> ((IActionHost)(Object)this).getActionableNode() ); eap$link = new WirelessSlaveLink(endpoint); - ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 创建新的无线链接"); + Logger.EAP$LOGGER.debug("[服务端] StorageBus 创建新的无线链接"); } + // 设置频道卡的所有者UUID(如果有的话) + eap$link.setPlacerId(ownerUUID); eap$link.setFrequency(channel); eap$link.updateStatus(); - ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); + Logger.EAP$LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); } catch (Exception e) { - ExtendedAELogger.LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e); + Logger.EAP$LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java index 202bc80..2227e45 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java @@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.parts.storagebus; import appeng.api.networking.IGridNode; import appeng.api.networking.ticking.TickRateModulation; import appeng.parts.storagebus.StorageBusPart; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -17,7 +17,7 @@ public abstract class StorageBusPartTickerChannelCardMixin { @Inject(method = "tickingRequest", at = @At("TAIL")) private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { - if (this instanceof InterfaceWirelessLinkBridge bridge) { + if (this instanceof IInterfaceWirelessLinkBridge bridge) { bridge.eap$updateWirelessLink(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java b/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java index 6a1621e..31cbfd9 100644 --- a/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java @@ -6,8 +6,8 @@ import appeng.api.upgrades.UpgradeInventories; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import com.extendedae_plus.compat.UpgradeSlotCompat; +import com.extendedae_plus.util.Logger; 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; @@ -28,10 +28,10 @@ public class AppfluxPatternProviderLogicMixin { at = @At("TAIL")) private void eap$modifyAppfluxUpgradeSlots(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { try { - + // 只有当appflux存在且不启用我们的升级槽时才修改数量 if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && UpgradeSlotCompat.shouldEnableChannelCard()) { - + // 使用反射找到appflux的升级槽字段并替换 try { Field upgradesField = this.getClass().getDeclaredField("af_$upgrades"); @@ -39,7 +39,7 @@ public class AppfluxPatternProviderLogicMixin { IUpgradeInventory currentUpgrades = (IUpgradeInventory) upgradesField.get(this); if (currentUpgrades != null) { - + // 创建新的2槽升级槽 IUpgradeInventory newUpgrades = UpgradeInventories.forMachine( host.getTerminalIcon().getItem(), @@ -49,7 +49,7 @@ public class AppfluxPatternProviderLogicMixin { // 调用appflux的升级变更方法 this.getClass().getDeclaredMethod("af_$onUpgradesChanged").invoke(this); } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("调用appflux升级变更方法失败", e); + Logger.EAP$LOGGER.error("调用appflux升级变更方法失败", e); } } ); @@ -65,11 +65,11 @@ public class AppfluxPatternProviderLogicMixin { upgradesField.set(this, newUpgrades); } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("反射修改appflux升级槽失败", e); + Logger.EAP$LOGGER.error("反射修改appflux升级槽失败", e); } } } catch (Exception e) { - com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e); + Logger.EAP$LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java index 4009b1b..1b27516 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternProviderMixin.java @@ -4,8 +4,9 @@ import appeng.client.gui.Icon; import appeng.client.gui.implementations.PatternProviderScreen; import appeng.client.gui.style.ScreenStyle; import appeng.menu.SlotSemantics; -import com.extendedae_plus.NewIcon; -import com.extendedae_plus.api.ExPatternButtonsAccessor; +import com.extendedae_plus.ae.client.gui.NewIcon; +import com.extendedae_plus.api.IExPatternButtonsAccessor; +import com.extendedae_plus.api.IExPatternPageAccessor; import com.extendedae_plus.config.ModConfig; import com.glodblock.github.extendedae.client.button.ActionEPPButton; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; @@ -23,10 +24,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.lang.reflect.Field; import java.lang.reflect.Method; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; +import static com.extendedae_plus.util.Logger.EAP$LOGGER; @Mixin(GuiExPatternProvider.class) -public abstract class GuiExPatternProviderMixin extends PatternProviderScreen implements ExPatternButtonsAccessor, com.extendedae_plus.api.ExPatternPageAccessor { +public abstract class GuiExPatternProviderMixin extends PatternProviderScreen implements IExPatternButtonsAccessor, IExPatternPageAccessor { @Unique ScreenStyle eap$screenStyle; diff --git a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java index 143aef9..af13e76 100644 --- a/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/extendedae/client/gui/GuiExPatternTerminalMixin.java @@ -11,7 +11,7 @@ import appeng.menu.AEBaseMenu; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; -import com.extendedae_plus.network.OpenProviderUiC2SPacket; +import com.extendedae_plus.network.provider.OpenProviderUiC2SPacket; import com.extendedae_plus.util.GuiUtil; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import net.minecraft.client.Minecraft; diff --git a/src/main/java/com/extendedae_plus/network/ChannelCardBindPacket.java b/src/main/java/com/extendedae_plus/network/ChannelCardBindPacket.java new file mode 100644 index 0000000..6ead989 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/ChannelCardBindPacket.java @@ -0,0 +1,77 @@ +package com.extendedae_plus.network; + +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.util.WirelessTeamUtil; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent; + +import java.util.UUID; +import java.util.function.Supplier; + +/** + * 频道卡绑定网络包 + * 客户端发送到服务端,用于处理左键空气的绑定/解绑操作 + */ +public class ChannelCardBindPacket { + + private final InteractionHand hand; + + public ChannelCardBindPacket(InteractionHand hand) { + this.hand = hand; + } + + public static void encode(ChannelCardBindPacket packet, FriendlyByteBuf buf) { + buf.writeEnum(packet.hand); + } + + public static ChannelCardBindPacket decode(FriendlyByteBuf buf) { + return new ChannelCardBindPacket(buf.readEnum(InteractionHand.class)); + } + + public static void handle(ChannelCardBindPacket packet, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player == null) { + return; + } + + ItemStack stack = player.getItemInHand(packet.hand); + if (stack.getItem() != ModItems.CHANNEL_CARD.get()) { + return; + } + + ServerLevel level = player.serverLevel(); + UUID currentOwner = ChannelCardItem.getOwnerUUID(stack); + + if (currentOwner != null) { + // 已有所有者,清除 + ChannelCardItem.clearOwner(stack); + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.cleared"), + true + ); + } else { + // 写入当前玩家的UUID和团队信息 + UUID playerUUID = player.getUUID(); + ChannelCardItem.setOwnerUUID(stack, playerUUID); + + // 获取团队名称用于显示 + Component teamName = WirelessTeamUtil.getNetworkOwnerName(level, playerUUID); + ChannelCardItem.setTeamName(stack, teamName.getString()); + + player.displayClientMessage( + Component.translatable("item.extendedae_plus.channel_card.owner.bound", teamName), + true + ); + } + }); + ctx.get().setPacketHandled(true); + } +} + diff --git a/src/main/java/com/extendedae_plus/network/SetBlockHighlightS2CPacket.java b/src/main/java/com/extendedae_plus/network/SetBlockHighlightS2CPacket.java new file mode 100644 index 0000000..3b076b1 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/SetBlockHighlightS2CPacket.java @@ -0,0 +1,77 @@ +package com.extendedae_plus.network; + +import com.glodblock.github.extendedae.client.render.EAEHighlightHandler; +import com.glodblock.github.extendedae.util.FCClientUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * S2C: 指示客户端对某个方块位置进行高亮(仅作用于接收该包的客户端) + */ +public class SetBlockHighlightS2CPacket { + private final BlockPos pos; + private final Direction face; // 可为 null,表示方块形 + private final ResourceLocation dim; // 维度的 ResourceLocation + private final long durationMillis; // 持续时间(毫秒) + + public SetBlockHighlightS2CPacket(BlockPos pos, Direction face, ResourceLocation dim, long durationMillis) { + this.pos = pos; + this.face = face; + this.dim = dim; + this.durationMillis = durationMillis; + } + + public static void encode(SetBlockHighlightS2CPacket pkt, FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeBoolean(pkt.face != null); + if (pkt.face != null) buf.writeEnum(pkt.face); + buf.writeResourceLocation(pkt.dim); + buf.writeLong(pkt.durationMillis); + } + + public static SetBlockHighlightS2CPacket decode(FriendlyByteBuf buf) { + var pos = buf.readBlockPos(); + Direction face = null; + if (buf.readBoolean()) face = buf.readEnum(Direction.class); + var dim = buf.readResourceLocation(); + long dur = buf.readLong(); + return new SetBlockHighlightS2CPacket(pos, face, dim, dur); + } + + public static void handle(SetBlockHighlightS2CPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + try { + // 在客户端执行高亮 + ResourceKey dimKey = ResourceKey.create(Registries.DIMENSION, msg.dim); + long endTime = System.currentTimeMillis() + msg.durationMillis; + if (msg.face == null) { + EAEHighlightHandler.highlight(msg.pos, dimKey, endTime); + } else { + var origin = new AABB(msg.pos); + switch (msg.face) { + case WEST -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) (Math.PI / 2)); + case SOUTH -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) Math.PI); + case EAST -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.Y, (float) (-Math.PI / 2)); + case UP -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.X, (float) (-Math.PI / 2)); + case DOWN -> origin = FCClientUtil.rotor(origin, origin.getCenter(), Direction.Axis.X, (float) (Math.PI / 2)); + } + EAEHighlightHandler.highlight(msg.pos, msg.face, dimKey, endTime, origin); + } + } catch (Throwable ignored) { + } + }); + ctx.setPacketHandled(true); + } +} + + diff --git a/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java b/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java new file mode 100644 index 0000000..31413a5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/SetWirelessFrequencyC2SPacket.java @@ -0,0 +1,85 @@ +package com.extendedae_plus.network; + +import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * 设置无线收发器频率的网络数据包 + * 客户端发送到服务端,用于通过输入框设置频率 + */ +public class SetWirelessFrequencyC2SPacket { + + private final BlockPos pos; + private final long frequency; + + public SetWirelessFrequencyC2SPacket(BlockPos pos, long frequency) { + this.pos = pos; + this.frequency = frequency; + } + + public static void encode(SetWirelessFrequencyC2SPacket packet, FriendlyByteBuf buf) { + buf.writeBlockPos(packet.pos); + buf.writeLong(packet.frequency); + } + + public static SetWirelessFrequencyC2SPacket decode(FriendlyByteBuf buf) { + BlockPos pos = buf.readBlockPos(); + long frequency = buf.readLong(); + return new SetWirelessFrequencyC2SPacket(pos, frequency); + } + + public static void handle(SetWirelessFrequencyC2SPacket packet, Supplier ctx) { + ctx.get().enqueueWork(() -> { + ServerPlayer player = ctx.get().getSender(); + if (player == null) { + return; + } + + // 检查方块实体是否存在 + var level = player.serverLevel(); + if (!level.hasChunkAt(packet.pos)) { + return; + } + + var blockEntity = level.getBlockEntity(packet.pos); + if (!(blockEntity instanceof WirelessTransceiverBlockEntity te)) { + return; + } + + // 通过GUI设置频率时,忽略锁定状态 + // 临时保存锁定状态 + boolean wasLocked = te.isLocked(); + + // 临时解锁以允许设置 + if (wasLocked) { + te.setLocked(false); + } + + // 设置频率 + long newFreq = packet.frequency; + if (newFreq < 0) { + newFreq = 0; + } + te.setFrequency(newFreq); + + // 恢复锁定状态 + if (wasLocked) { + te.setLocked(true); + } + + // 发送反馈消息 + player.displayClientMessage( + Component.literal("频道已设置为:" + te.getFrequency()), + true + ); + }); + ctx.get().setPacketHandled(true); + } +} + diff --git a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java similarity index 97% rename from src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java rename to src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java index 3a547aa..b3206d2 100644 --- a/src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorJumpC2SPacket.java @@ -1,8 +1,7 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.crafting; import appeng.api.crafting.IPatternDetails; import appeng.api.networking.IGrid; -import appeng.api.networking.crafting.ICraftingProvider; import appeng.api.networking.security.IActionHost; import appeng.api.stacks.AEKey; import appeng.helpers.patternprovider.PatternProviderLogic; @@ -18,7 +17,6 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraftforge.network.NetworkEvent; diff --git a/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorOpenProviderC2SPacket.java b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorOpenProviderC2SPacket.java new file mode 100644 index 0000000..7fea21f --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/crafting/CraftingMonitorOpenProviderC2SPacket.java @@ -0,0 +1,205 @@ +package com.extendedae_plus.network.crafting; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.networking.IGrid; +import appeng.api.networking.security.IActionHost; +import appeng.api.stacks.AEKey; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.me.service.CraftingService; +import appeng.menu.locator.MenuLocators; +import appeng.menu.me.crafting.CraftingCPUMenu; +import appeng.parts.AEBasePart; +import com.extendedae_plus.init.ModNetwork; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor; +import com.extendedae_plus.network.SetBlockHighlightS2CPacket; +import com.extendedae_plus.network.SetPatternHighlightS2CPacket; +import com.extendedae_plus.network.provider.SetProviderPageS2CPacket; +import com.extendedae_plus.util.PatternProviderDataUtil; +import com.glodblock.github.glodium.util.GlodUtil; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; + +import java.util.Collection; +import java.util.function.Supplier; + +/** + * 网络包:客户端向服务器发送请求,打开与 AEKey 对应的 PatternProvider UI。 + *

+ * 流程: + * 1. 客户端发送 AEKey。 + * 2. 服务端根据当前 CraftingCPUMenu 获取对应 Grid。 + * 3. 定位所有提供该 AEKey 的 PatternProvider。 + * 4. 打开 Provider UI,并向客户端发送高亮和页码信息。 + */ +public class CraftingMonitorOpenProviderC2SPacket { + private final AEKey what; + + public CraftingMonitorOpenProviderC2SPacket(AEKey what) { + this.what = what; + } + + public static void encode(CraftingMonitorOpenProviderC2SPacket msg, FriendlyByteBuf buf) { + AEKey.writeKey(buf, msg.what); + } + + public static CraftingMonitorOpenProviderC2SPacket decode(FriendlyByteBuf buf) { + return new CraftingMonitorOpenProviderC2SPacket(AEKey.readKey(buf)); + } + + public static void handle(CraftingMonitorOpenProviderC2SPacket msg, Supplier ctx) { + var context = ctx.get(); + context.enqueueWork(() -> { + ServerPlayer player = context.getSender(); + if (player == null || !(player.containerMenu instanceof CraftingCPUMenu menu)) return; + + // 从菜单获取 Grid + IGrid grid = GridHelper.getGridFromMenu(menu); + if (grid == null) return; + + // 获取 CraftingService + var cs = grid.getCraftingService(); + if (!(cs instanceof CraftingService craftingService)) return; + + // 根据 AEKey 查找所有匹配样板 + Collection patterns = craftingService.getCraftingFor(msg.what); + if (patterns.isEmpty()) return; + + // 遍历所有样板,找到第一个可用 Provider 并打开 UI + for (var pattern : patterns) { + var provider = PatternLocator.findValidProvider(craftingService, pattern, grid); + if (provider == null) continue; + + try { + ProviderUIHelper.openProviderUI(provider, pattern, player); + } catch (Exception ignored) {} + } + }); + context.setPacketHandled(true); + } + + // ===================== 内部工具类 ===================== + + /** + * GridHelper: 从菜单中获取网格实例 + */ + public static final class GridHelper { + private GridHelper() {} + + /** + * 获取菜单对应的 Grid + * @param menu 当前 AEBaseMenu + * @return Grid 或 null + */ + public static IGrid getGridFromMenu(appeng.menu.AEBaseMenu menu) { + Object target = menu.getTarget(); + if (target instanceof IActionHost host && host.getActionableNode() != null) { + return host.getActionableNode().getGrid(); + } + return null; + } + } + + /** + * PatternLocator: 根据样板定位可用的 Provider + */ + public static final class PatternLocator { + private PatternLocator() {} + + /** + * 查找提供指定样板的可用 Provider + * @param cs CraftingService + * @param pattern 样板 + * @param grid 当前 Grid + * @return 第一个可用的 PatternProviderLogic 或 null + */ + public static PatternProviderLogic findValidProvider(CraftingService cs, IPatternDetails pattern, IGrid grid) { + var providers = cs.getProviders(pattern); + for (var provider : providers) { + if (provider instanceof PatternProviderLogic ppl) { + var host = ((PatternProviderLogicAccessor) ppl).eap$host(); + if (host == null || host.getBlockEntity() == null) continue; + if (!PatternProviderDataUtil.isProviderAvailable(ppl, grid)) continue; + return ppl; + } + } + return null; + } + } + + /** + * ProviderUIHelper: 打开 Provider UI 并发送客户端反馈 + */ + public static final class ProviderUIHelper { + private ProviderUIHelper() {} + + /** + * 打开 Provider UI + * 1. 打开菜单 + * 2. 发送高亮包 + * 3. 发送页码包 + * 4. 发送 Pattern 输出高亮包 + * + * @param provider PatternProviderLogic 实例 + * @param pattern 样板 + * @param player 玩家 + */ + public static void openProviderUI(PatternProviderLogic provider, IPatternDetails pattern, ServerPlayer player) { + var host = ((PatternProviderLogicAccessor) provider).eap$host(); + var pbe = host.getBlockEntity(); + if (pbe == null) return; + + boolean isPart = host instanceof AEBasePart part; + var locator = isPart ? MenuLocators.forPart((AEBasePart) host) : MenuLocators.forBlockEntity(pbe); + host.openMenu(player, locator); + + // 高亮显示 + ModNetwork.CHANNEL.sendTo( + new SetBlockHighlightS2CPacket( + pbe.getBlockPos(), + isPart ? ((AEBasePart) host).getSide() : null, + pbe.getLevel().dimension().location(), + (long) (6000 * GlodUtil.clamp(1.0, 1, 30)) + ), + player.connection.connection, + NetworkDirection.PLAY_TO_CLIENT + ); + + // 聊天提示 + player.displayClientMessage( + Component.translatable( + "chat.ex_pattern_access_terminal.pos", + pbe.getBlockPos().toShortString(), + pbe.getLevel().dimension().location().getPath() + ), + false + ); + + // 页码同步 + int slot = PatternProviderDataUtil.findSlotForPattern(provider, pattern.getDefinition()); + if (slot >= 0) { + int page = slot / 36; + if (page > 0) { + ModNetwork.CHANNEL.sendTo( + new SetProviderPageS2CPacket(page), + player.connection.connection, + NetworkDirection.PLAY_TO_CLIENT + ); + } + } + + // 输出高亮 + var outputs = pattern.getOutputs(); + if (outputs != null && outputs.length > 0 && outputs[0] != null) { + AEKey key = outputs[0].what(); + ModNetwork.CHANNEL.sendTo( + new SetPatternHighlightS2CPacket(key, true), + player.connection.connection, + NetworkDirection.PLAY_TO_CLIENT + ); + } + } + } +} diff --git a/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java b/src/main/java/com/extendedae_plus/network/crafting/OpenCraftFromJeiC2SPacket.java similarity index 98% rename from src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java rename to src/main/java/com/extendedae_plus/network/crafting/OpenCraftFromJeiC2SPacket.java index 5a9a33d..ac4f48e 100644 --- a/src/main/java/com/extendedae_plus/network/OpenCraftFromJeiC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/crafting/OpenCraftFromJeiC2SPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.crafting; import appeng.api.networking.IGrid; import appeng.api.stacks.AEKey; diff --git a/src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java b/src/main/java/com/extendedae_plus/network/meInterface/InterfaceAdjustConfigAmountC2SPacket.java similarity index 98% rename from src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java rename to src/main/java/com/extendedae_plus/network/meInterface/InterfaceAdjustConfigAmountC2SPacket.java index 8d899ed..f563ebb 100644 --- a/src/main/java/com/extendedae_plus/network/InterfaceAdjustConfigAmountC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/meInterface/InterfaceAdjustConfigAmountC2SPacket.java @@ -1,8 +1,7 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.meInterface; -import appeng.menu.implementations.InterfaceMenu; -import appeng.menu.SlotSemantics; import appeng.api.stacks.GenericStack; +import appeng.menu.implementations.InterfaceMenu; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; diff --git a/src/main/java/com/extendedae_plus/network/AdvancedBlockingSyncS2CPacket.java b/src/main/java/com/extendedae_plus/network/provider/AdvancedBlockingSyncS2CPacket.java similarity index 96% rename from src/main/java/com/extendedae_plus/network/AdvancedBlockingSyncS2CPacket.java rename to src/main/java/com/extendedae_plus/network/provider/AdvancedBlockingSyncS2CPacket.java index 357d0f3..eccbd30 100644 --- a/src/main/java/com/extendedae_plus/network/AdvancedBlockingSyncS2CPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/AdvancedBlockingSyncS2CPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import com.extendedae_plus.client.ClientAdvancedBlockingState; import net.minecraft.network.FriendlyByteBuf; diff --git a/src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/GlobalToggleProviderModesC2SPacket.java similarity index 95% rename from src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java rename to src/main/java/com/extendedae_plus/network/provider/GlobalToggleProviderModesC2SPacket.java index c37d0bc..a07dc5d 100644 --- a/src/main/java/com/extendedae_plus/network/GlobalToggleProviderModesC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/GlobalToggleProviderModesC2SPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import appeng.api.config.Settings; import appeng.api.config.YesNo; @@ -7,8 +7,8 @@ import appeng.blockentity.crafting.PatternProviderBlockEntity; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.parts.crafting.PatternProviderPart; -import com.extendedae_plus.api.AdvancedBlockingHolder; -import com.extendedae_plus.api.SmartDoublingHolder; +import com.extendedae_plus.api.IAdvancedBlocking; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity; import net.minecraft.core.BlockPos; import net.minecraft.network.FriendlyByteBuf; @@ -16,15 +16,15 @@ import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; -import java.util.Set; import java.util.HashSet; +import java.util.Set; import java.util.function.Supplier; /** * C2S:全网批量切换样板供应器的三种模式: * - 阻挡模式(AE2 内置 BLOCKING_MODE 设置) - * - 高级阻挡模式(AdvancedBlockingHolder mixin) - * - 智能翻倍模式(SmartDoublingHolder mixin) + * - 高级阻挡模式(IAdvancedBlocking mixin) + * - 智能翻倍模式(ISmartDoublingHolder mixin) * * 负载为三个操作码(各1字节),分别对应:blocking、advancedBlocking、smartDoubling。 */ @@ -173,14 +173,14 @@ public class GlobalToggleProviderModesC2SPacket { } } // 2) 高级阻挡(mixin 接口) - if (msg.opAdvancedBlocking != Op.NOOP && logic instanceof AdvancedBlockingHolder adv) { + if (msg.opAdvancedBlocking != Op.NOOP && logic instanceof IAdvancedBlocking adv) { boolean current = adv.eap$getAdvancedBlocking(); boolean target = computeTarget(current, msg.opAdvancedBlocking); adv.eap$setAdvancedBlocking(target); changed = changed || (current != target); } // 3) 智能翻倍(mixin 接口) - if (msg.opSmartDoubling != Op.NOOP && logic instanceof SmartDoublingHolder sd) { + if (msg.opSmartDoubling != Op.NOOP && logic instanceof ISmartDoublingHolder sd) { boolean current = sd.eap$getSmartDoubling(); boolean target = computeTarget(current, msg.opSmartDoubling); sd.eap$setSmartDoubling(target); diff --git a/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/OpenProviderUiC2SPacket.java similarity index 99% rename from src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java rename to src/main/java/com/extendedae_plus/network/provider/OpenProviderUiC2SPacket.java index 8a02170..098c095 100644 --- a/src/main/java/com/extendedae_plus/network/OpenProviderUiC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/OpenProviderUiC2SPacket.java @@ -1,20 +1,20 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.core.Direction; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.InteractionResult; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; -import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkHooks; diff --git a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java b/src/main/java/com/extendedae_plus/network/provider/ProvidersListS2CPacket.java similarity index 97% rename from src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java rename to src/main/java/com/extendedae_plus/network/provider/ProvidersListS2CPacket.java index 87d3227..02689dc 100644 --- a/src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/ProvidersListS2CPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import com.extendedae_plus.client.ui.ProviderSelectScreen; import net.minecraft.client.Minecraft; diff --git a/src/main/java/com/extendedae_plus/network/RequestProvidersListC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java similarity index 98% rename from src/main/java/com/extendedae_plus/network/RequestProvidersListC2SPacket.java rename to src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java index d9a0cc3..919ba09 100644 --- a/src/main/java/com/extendedae_plus/network/RequestProvidersListC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/RequestProvidersListC2SPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import appeng.helpers.patternprovider.PatternContainer; import appeng.menu.implementations.PatternAccessTermMenu; diff --git a/src/main/java/com/extendedae_plus/network/provider/SetPerProviderScalingLimitC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/SetPerProviderScalingLimitC2SPacket.java new file mode 100644 index 0000000..4537c47 --- /dev/null +++ b/src/main/java/com/extendedae_plus/network/provider/SetPerProviderScalingLimitC2SPacket.java @@ -0,0 +1,61 @@ +package com.extendedae_plus.network.provider; + +import appeng.menu.implementations.PatternProviderMenu; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; +import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAccessor; +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; + +/** + * C2S: set per-provider scaling limit for the currently opened provider and pattern index + */ +public class SetPerProviderScalingLimitC2SPacket { + private final int limit; + + public SetPerProviderScalingLimitC2SPacket(int limit) { + this.limit = limit; + } + + public static void encode(SetPerProviderScalingLimitC2SPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.limit); + } + + public static SetPerProviderScalingLimitC2SPacket decode(FriendlyByteBuf buf) { + return new SetPerProviderScalingLimitC2SPacket(buf.readInt()); + } + + public static void handle(SetPerProviderScalingLimitC2SPacket msg, Supplier ctxSupplier) { + var ctx = ctxSupplier.get(); + ctx.enqueueWork(() -> { + try { + ServerPlayer player = ctx.getSender(); + if (player == null) return; + var containerMenu = player.containerMenu; + if (containerMenu instanceof PatternProviderMenu menu) { + var accessor = (PatternProviderMenuAccessor) menu; + var logic = accessor.eap$logic(); + if (logic instanceof ISmartDoublingHolder handler) { + handler.eap$setProviderSmartDoublingLimit(msg.limit); + logic.saveChanges(); + } + } else if (containerMenu instanceof AdvPatternProviderMenu advMenu) { + var accessor = (AdvPatternProviderMenuAdvancedAccessor) advMenu; + var logic = accessor.eap$logic(); + if (logic instanceof ISmartDoublingHolder handler) { + handler.eap$setProviderSmartDoublingLimit(msg.limit); + logic.saveChanges(); + } + } + } catch (Throwable ignored) { + } + }); + ctx.setPacketHandled(true); + } +} + + diff --git a/src/main/java/com/extendedae_plus/network/SetProviderPageS2CPacket.java b/src/main/java/com/extendedae_plus/network/provider/SetProviderPageS2CPacket.java similarity index 97% rename from src/main/java/com/extendedae_plus/network/SetProviderPageS2CPacket.java rename to src/main/java/com/extendedae_plus/network/provider/SetProviderPageS2CPacket.java index 1c57644..5bfd936 100644 --- a/src/main/java/com/extendedae_plus/network/SetProviderPageS2CPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/SetProviderPageS2CPacket.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import appeng.menu.SlotSemantics; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; diff --git a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/ToggleAdvancedBlockingC2SPacket.java similarity index 89% rename from src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java rename to src/main/java/com/extendedae_plus/network/provider/ToggleAdvancedBlockingC2SPacket.java index 2711206..9162194 100644 --- a/src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/ToggleAdvancedBlockingC2SPacket.java @@ -1,11 +1,11 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; 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.api.IAdvancedBlocking; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor; -import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAccessor; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; @@ -34,9 +34,9 @@ public class ToggleAdvancedBlockingC2SPacket { var containerMenu = player.containerMenu; if (containerMenu instanceof PatternProviderMenu menu) { - var accessor = (PatternProviderMenuAdvancedAccessor) menu; + var accessor = (PatternProviderMenuAccessor) menu; var logic = accessor.eap$logic(); - if (logic instanceof AdvancedBlockingHolder holder) { + if (logic instanceof IAdvancedBlocking holder) { boolean current = holder.eap$getAdvancedBlocking(); boolean next = !current; holder.eap$setAdvancedBlocking(next); @@ -48,7 +48,7 @@ public class ToggleAdvancedBlockingC2SPacket { }else if (containerMenu instanceof AdvPatternProviderMenu menu){ var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu; var logic = accessor.eap$logic(); - if (logic instanceof AdvancedBlockingHolder holder) { + if (logic instanceof IAdvancedBlocking holder) { boolean current = holder.eap$getAdvancedBlocking(); boolean next = !current; holder.eap$setAdvancedBlocking(next); diff --git a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java b/src/main/java/com/extendedae_plus/network/provider/ToggleSmartDoublingC2SPacket.java similarity index 86% rename from src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java rename to src/main/java/com/extendedae_plus/network/provider/ToggleSmartDoublingC2SPacket.java index 88d832a..2006b5c 100644 --- a/src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java +++ b/src/main/java/com/extendedae_plus/network/provider/ToggleSmartDoublingC2SPacket.java @@ -1,9 +1,9 @@ -package com.extendedae_plus.network; +package com.extendedae_plus.network.provider; import appeng.menu.implementations.PatternProviderMenu; -import com.extendedae_plus.api.SmartDoublingHolder; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor; -import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor; +import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAccessor; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.network.NetworkEvent; @@ -31,9 +31,9 @@ public class ToggleSmartDoublingC2SPacket { if (player == null) return; var containerMenu = player.containerMenu; if (containerMenu instanceof PatternProviderMenu menu) { - var accessor = (PatternProviderMenuAdvancedAccessor) menu; + var accessor = (PatternProviderMenuAccessor) menu; var logic = accessor.eap$logic(); - if (logic instanceof SmartDoublingHolder holder) { + if (logic instanceof ISmartDoublingHolder holder) { boolean current = holder.eap$getSmartDoubling(); boolean next = !current; holder.eap$setSmartDoubling(next); @@ -42,7 +42,7 @@ public class ToggleSmartDoublingC2SPacket { }else if (containerMenu instanceof AdvPatternProviderMenu menu){ var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu; var logic = accessor.eap$logic(); - if (logic instanceof SmartDoublingHolder holder) { + if (logic instanceof ISmartDoublingHolder holder) { boolean current = holder.eap$getSmartDoubling(); boolean next = !current; holder.eap$setSmartDoubling(next); diff --git a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java b/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java deleted file mode 100644 index 6323978..0000000 --- a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.extendedae_plus.util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * 配置解析工具类:用于解析黑名单与倍率配置的字符串 - */ -public final class ConfigParsingUtils { - private ConfigParsingUtils() {} - - public static final class MultiplierEntry { - public final Pattern pattern; - public final double multiplier; - - public MultiplierEntry(Pattern pattern, double multiplier) { - this.pattern = pattern; - this.multiplier = multiplier; - } - } - - /** - * 编译用户提供的匹配串。支持简单的 glob 语法('*' 和 '?')以及完整的正则表达式。 - * 规则: - * - 如果字符串包含 ".*" 或其他正则元字符,优先尝试按正则编译(若失败则回退到 glob 转换)。 - * - 否则若包含 '*' 或 '?',将按 glob 语法转换为正则。 - * - 否则按字面量匹配处理。 - */ - public static Pattern compilePattern(String raw) { - if (raw == null) throw new IllegalArgumentException("pattern is null"); - raw = raw.trim(); - if (raw.isEmpty()) throw new IllegalArgumentException("pattern is empty"); - - // If it looks like regex (contains '.*' or regex metachar), try regex first - if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) { - try { - return Pattern.compile("^" + raw + "$"); - } catch (PatternSyntaxException ignored) { - // fallback to glob below - } - } - - // If contains glob chars, convert to regex - if (raw.contains("*") || raw.contains("?")) { - StringBuilder sb = new StringBuilder(); - sb.append('^'); - 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('\\'); - } - sb.append(c); - } - } - sb.append('$'); - return Pattern.compile(sb.toString()); - } - - // Otherwise treat as a literal (match exact) - return Pattern.compile("^" + Pattern.quote(raw) + "$"); - } - - /** - * Parse multiplier entries like 'modid:block 2x' into MultiplierEntry objects. - * Accepts values with optional trailing 'x' (case-insensitive). - */ - public static MultiplierEntry parseMultiplierEntry(String entry) { - if (entry == null) return null; - String[] parts = entry.trim().split("\\s+"); - if (parts.length < 2) return null; - String key = parts[0]; - String val = parts[1].toLowerCase(); - if (val.endsWith("x")) val = val.substring(0, val.length() - 1); - double m; - try { - m = Double.parseDouble(val); - } catch (NumberFormatException ex) { - return null; - } - try { - Pattern p = compilePattern(key); - return new MultiplierEntry(p, m); - } catch (IllegalArgumentException e) { - return null; - } - } - - 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) {} - } - return out; - } - - public static List parseMultiplierList(List raw) { - List out = new ArrayList<>(); - if (raw == null) return out; - for (String s : raw) { - MultiplierEntry me = parseMultiplierEntry(s); - if (me != null) out.add(me); - } - return out; - } - - // ------------------ 全局缓存与接口 ------------------ - private static volatile List cachedBlacklist = null; - private static volatile List cachedMultiplierEntries = null; - private static volatile List cachedBlacklistSourceSnapshot = null; - private static volatile List cachedMultiplierSourceSnapshot = null; - private static final Object CACHE_LOCK = new Object(); - - /** - * 获取已解析并缓存的黑名单(线程安全、懒加载)。 - */ - public static List getCachedBlacklist(java.util.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); - cachedBlacklistSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized); - } - return Collections.unmodifiableList(cachedBlacklist); - } - } - - /** - * 获取已解析并缓存的倍率列表(线程安全、懒加载)。 - */ - public static List getCachedMultiplierEntries(java.util.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); - cachedMultiplierSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized); - } - return Collections.unmodifiableList(cachedMultiplierEntries); - } - } - - // Normalize the incoming source list: trim entries, drop blanks, keep stable ordering - private static List normalizeSource(java.util.List source) { - List out = new ArrayList<>(); - if (source == null) return out; - for (String s : source) { - if (s == null) continue; - String t = s.trim(); - if (t.isEmpty()) continue; - out.add(t); - } - return out; - } - - // Null-safe equality for two lists of strings. Uses size + element equals. - private static boolean listEquals(List a, List b) { - if (a == b) return true; - if (a == null || b == null) return false; - if (a.size() != b.size()) return false; - for (int i = 0; i < a.size(); i++) { - if (!a.get(i).equals(b.get(i))) return false; - } - return true; - } - - /** - * 清空缓存,下一次获取时将重新从提供的源解析(或调用方可以重新调用 getter)。 - */ - public static void reload() { - synchronized (CACHE_LOCK) { - cachedBlacklist = null; - cachedMultiplierEntries = null; - } - } -} - - diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 0882492..460e7b0 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -1004,7 +1004,9 @@ public class ExtendedAEPatternUploadUtil { // 尝试获取供应器的组信息来构建显示名称 var group = container.getTerminalGroup(); if (group != null) { - return group.name().getString(); + // 使用 Component 序列化来保持翻译键,而不是直接 getString() + // 这样客户端可以根据自己的语言设置进行翻译 + return Component.Serializer.toJson(group.name()); } } catch (Exception e) { // 忽略异常,使用默认名称 @@ -1252,7 +1254,11 @@ public class ExtendedAEPatternUploadUtil { if (container == null) return "未知供应器"; try { var group = container.getTerminalGroup(); - if (group != null) return group.name().getString(); + if (group != null) { + // 使用 Component 序列化来保持翻译键,而不是直接 getString() + // 这样客户端可以根据自己的语言设置进行翻译 + return Component.Serializer.toJson(group.name()); + } } catch (Throwable ignored) { } return "样板供应器"; 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/GuiUtil.java b/src/main/java/com/extendedae_plus/util/GuiUtil.java index 1c804d9..4425d64 100644 --- a/src/main/java/com/extendedae_plus/util/GuiUtil.java +++ b/src/main/java/com/extendedae_plus/util/GuiUtil.java @@ -1,17 +1,24 @@ package com.extendedae_plus.util; +import appeng.api.config.Settings; +import appeng.api.config.YesNo; import appeng.api.crafting.PatternDetailsHelper; import appeng.api.stacks.GenericStack; import appeng.client.gui.me.patternaccess.PatternContainerRecord; import appeng.client.gui.me.patternaccess.PatternSlot; +import appeng.client.gui.widgets.SettingToggleButton; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import java.util.List; import java.util.Set; +import java.util.function.Supplier; +import java.util.function.IntConsumer; /** @@ -184,4 +191,42 @@ public class GuiUtil { int backgroundColor = withAlpha(rainbowRgb, 0x3C); drawSlotBox(guiGraphics, sx, sy, borderColor, backgroundColor); } + + public static SettingToggleButton createToggle(boolean initial, + Runnable onClick, + Supplier> tooltipSupplier) { + return new SettingToggleButton<>( + Settings.BLOCKING_MODE, + initial ? YesNo.YES : YesNo.NO, + (btn, backwards) -> onClick.run() + ) { + @Override + public List getTooltipMessage() { + return tooltipSupplier.get(); + } + }; + } + + /** + * 创建用于每个提供者缩放上限的输入框,包含值清洗与回调处理 + * @param font 字体对象 + * @param initialValue 初始数值 + * @param onCommit 当值解析成功后回调(以 int 形式提供) + */ + public static EditBox createPerProviderLimitInput(Font font, int initialValue, IntConsumer onCommit) { + EditBox input = new EditBox(font, 0, 0, 28, 12, Component.literal("Limit")); + input.setMaxLength(6); + input.setValue(String.valueOf(initialValue)); + input.setResponder(s -> { + try { + String sValue = (s == null || s.isBlank()) ? "0" : s.replaceFirst("^0+(?=.)", ""); + if (!sValue.equals(s)) { + input.setValue(sValue); + } + int limit = Integer.parseInt(sValue); + onCommit.accept(limit); + } catch (Throwable ignored) {} + }); + return input; + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java b/src/main/java/com/extendedae_plus/util/Logger.java similarity index 55% rename from src/main/java/com/extendedae_plus/util/ExtendedAELogger.java rename to src/main/java/com/extendedae_plus/util/Logger.java index 728f5ab..b7ec4e8 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAELogger.java +++ b/src/main/java/com/extendedae_plus/util/Logger.java @@ -1,17 +1,16 @@ package com.extendedae_plus.util; import com.extendedae_plus.ExtendedAEPlus; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 统一日志工具类。 * 在需要的类中可使用: - * import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + * import static com.extendedae_plus.util.Logger.LOGGER; */ -public final class ExtendedAELogger { - public static final Logger LOGGER = LoggerFactory.getLogger(ExtendedAEPlus.MODID); - private ExtendedAELogger() { +public final class Logger { + public static final org.slf4j.Logger EAP$LOGGER = LoggerFactory.getLogger(ExtendedAEPlus.MODID); + private Logger() { // no instance throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } diff --git a/src/main/java/com/extendedae_plus/util/PatternProviderUIHelper.java b/src/main/java/com/extendedae_plus/util/PatternProviderUIHelper.java deleted file mode 100644 index 435a5a9..0000000 --- a/src/main/java/com/extendedae_plus/util/PatternProviderUIHelper.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.extendedae_plus.util; - -import appeng.client.gui.implementations.PatternProviderScreen; -import appeng.helpers.patternprovider.PatternProviderLogic; -import appeng.menu.implementations.PatternProviderMenu; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.Screen; - -/** - * 样板供应器UI辅助工具类 - * 用于在样板供应器界面中获取PatternProviderLogic对象 - */ -public class PatternProviderUIHelper { - - /** - * 获取当前打开的样板供应器的PatternProviderLogic - * - * @return PatternProviderLogic对象,如果当前没有打开样板供应器界面则返回null - */ - public static PatternProviderLogic getCurrentPatternProvider() { - Minecraft mc = Minecraft.getInstance(); - Screen currentScreen = mc.screen; - - if (currentScreen instanceof PatternProviderScreen patternScreen) { - PatternProviderMenu menu = (PatternProviderMenu) patternScreen.getMenu(); - - // 通过反射安全地访问protected字段 - try { - var logicField = PatternProviderMenu.class.getDeclaredField("logic"); - logicField.setAccessible(true); - return (PatternProviderLogic) logicField.get(menu); - } catch (Exception e) { - // 如果反射失败,返回null - return null; - } - } - - return null; - } - - /** - * 检查当前是否打开了样板供应器界面 - * - * @return 如果当前打开的是样板供应器界面则返回true - */ - public static boolean isPatternProviderScreenOpen() { - Minecraft mc = Minecraft.getInstance(); - return mc.screen instanceof PatternProviderScreen; - } - - /** - * 获取当前样板供应器界面的Screen对象 - * - * @return PatternProviderScreen对象,如果当前没有打开样板供应器界面则返回null - */ - public static PatternProviderScreen getCurrentPatternProviderScreen() { - Minecraft mc = Minecraft.getInstance(); - Screen currentScreen = mc.screen; - - if (currentScreen instanceof PatternProviderScreen patternScreen) { - return patternScreen; - } - - return null; - } - - /** - * 在当前样板供应器中执行样板数量倍增 - * - * @param multiplier 倍数(必须大于0) - * @return 缩放操作结果,如果当前没有打开样板供应器界面则返回null - */ - public static PatternProviderDataUtil.PatternScalingResult multiplyCurrentPatternAmounts(double multiplier) { - PatternProviderLogic patternProvider = getCurrentPatternProvider(); - if (patternProvider == null) { - return null; - } - - return PatternProviderDataUtil.multiplyPatternAmounts(patternProvider, multiplier); - } - - /** - * ExtendedAE风格的样板复制倍增 - * 提供更好的错误处理和恢复机制 - * - * @param multiplier 倍数(必须大于0) - * @return 缩放操作结果,如果当前没有打开样板供应器界面则返回null - */ - public static PatternProviderDataUtil.PatternScalingResult duplicateCurrentPatternAmountsExtendedAEStyle(double multiplier) { - PatternProviderLogic patternProvider = getCurrentPatternProvider(); - if (patternProvider == null) { - return null; - } - - return PatternProviderDataUtil.duplicatePatternAmountsExtendedAEStyle(patternProvider, multiplier); - } - - /** - * 在当前样板供应器中执行样板数量倍除 - * - * @param divisor 除数(必须大于0) - * @return 缩放操作结果,如果当前没有打开样板供应器界面则返回null - */ - public static PatternProviderDataUtil.PatternScalingResult divideCurrentPatternAmounts(double divisor) { - PatternProviderLogic patternProvider = getCurrentPatternProvider(); - if (patternProvider == null) { - return null; - } - - return PatternProviderDataUtil.dividePatternAmounts(patternProvider, divisor); - } - - /** - * 预览当前样板供应器的缩放效果 - * - * @param scaleFactor 缩放因子 - * @return 预览结果列表,如果当前没有打开样板供应器界面则返回空列表 - */ - public static java.util.List previewCurrentPatternScaling(double scaleFactor) { - PatternProviderLogic patternProvider = getCurrentPatternProvider(); - if (patternProvider == null) { - return new java.util.ArrayList<>(); - } - - return PatternProviderDataUtil.previewPatternScaling(patternProvider, scaleFactor); - } -} diff --git a/src/main/java/com/extendedae_plus/util/PatternScaler.java b/src/main/java/com/extendedae_plus/util/PatternScaler.java deleted file mode 100644 index 11a454f..0000000 --- a/src/main/java/com/extendedae_plus/util/PatternScaler.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.extendedae_plus.util; - -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.crafting.pattern.AEProcessingPattern; -import com.extendedae_plus.api.SmartDoublingAwarePattern; -import com.extendedae_plus.config.ModConfig; -import com.extendedae_plus.content.ScaledProcessingPattern; - -public final class PatternScaler { - private PatternScaler() { - } - - public static ScaledProcessingPattern scale(AEProcessingPattern base, AEKey target, long requestedAmount) { - if (base == null) throw new IllegalArgumentException("base"); - if (target == null) throw new IllegalArgumentException("target"); - - // 双保险:若样板标记为不允许缩放,直接放弃缩放(返回 null 表示调用方应保持原样板) - if (base instanceof SmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { - return null; - } - - GenericStack[] baseOutputs = base.getOutputs(); - - // 新逻辑:不再对样板进行单位化处理 - // 找到目标输出在 outputs 中的索引(尝试匹配 target,否则取第一个非空输出) - int targetOutIndex = -1; - for (int i = 0; i < baseOutputs.length; i++) { - var out = baseOutputs[i]; - if (out != null && target != null && out.what() != null && out.what().equals(target)) { - targetOutIndex = i; - break; - } - } - if (targetOutIndex == -1) { - for (int i = 0; i < baseOutputs.length; i++) { - if (baseOutputs[i] != null) { - targetOutIndex = i; - break; - } - } - } - if (targetOutIndex == -1 && baseOutputs.length > 0) targetOutIndex = 0; - - long perOperationTarget = 1L; - if (targetOutIndex >= 0 && baseOutputs[targetOutIndex] != null) { - long amt = baseOutputs[targetOutIndex].amount(); - if (amt > 0) perOperationTarget = amt; - } - - // 使用最小整数倍(ceil)策略:直接选择满足请求的最小倍数 - long multiplier = 1L; - if (requestedAmount > 0) { - long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1); - multiplier = needed <= 1L ? 1L : needed; - } - // 小请求绕过:若请求量小且不会带来收益,则不启用缩放(返回 null) - try { - int minBenefit = ModConfig.INSTANCE.smartScalingMinBenefitFactor; - if (minBenefit > 1 && requestedAmount > 0 && requestedAmount < perOperationTarget * (long) minBenefit) { - return null; - } - } catch (Throwable ignore) { - // 配置读取异常时保持默认行为(不绕过) - } - // 应用配置的最大倍数上限(0 表示不限制) - try { - int maxMul = ModConfig.INSTANCE.smartScalingMaxMultiplier; - if (maxMul > 0 && multiplier > maxMul) { - multiplier = maxMul; - } - } catch (Throwable ignore) { - // 配置读取异常时不施加上限 - } - - // 仅使用 multiplier 构建轻量化 ScaledProcessingPattern(具体视图按需计算) - return new ScaledProcessingPattern(base, base.getDefinition(), multiplier); - } -} 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 93c5001..0000000 --- a/src/main/java/com/extendedae_plus/util/PowerUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -package com.extendedae_plus.util; - -import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem; -import com.extendedae_plus.config.ModConfig; - -/** - * 用于计算实体加速器的能耗与加速倍率的工具类 - */ -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 = ModConfig.INSTANCE.entityTickerCost; - - // 计算以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 = ModConfig.INSTANCE.entityTickerCost; - 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/WirelessTeamUtil.java b/src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java new file mode 100644 index 0000000..51401ef --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/WirelessTeamUtil.java @@ -0,0 +1,221 @@ +package com.extendedae_plus.util; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.fml.ModList; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +/** + * 无线收发器队伍工具类 + * 实现FTBTeams软依赖:有FTBTeams时使用队伍UUID,没有时使用玩家UUID + */ +public class WirelessTeamUtil { + + private static final boolean FTB_TEAMS_LOADED = ModList.get().isLoaded("ftbteams"); + + /** + * 获取用于无线网络隔离的UUID + * - 如果安装了FTBTeams且玩家在队伍中,返回队伍UUID(同队玩家共享) + * - 否则返回玩家自己的UUID(独立网络) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 网络所有者UUID + */ + public static UUID getNetworkOwnerUUID(@Nullable ServerLevel level, UUID playerUUID) { + if (playerUUID == null) { + return null; + } + + if (!FTB_TEAMS_LOADED) { + return playerUUID; + } + + if (level == null) { + return playerUUID; + } + + try { + return getTeamUUID(level, playerUUID); + } catch (Exception e) { + // 如果FTBTeams API调用失败,回退到玩家UUID + return playerUUID; + } + } + + /** + * 获取网络所有者的显示名称(用于UI显示) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 显示名称 + */ + public static Component getNetworkOwnerName(@Nullable ServerLevel level, UUID playerUUID) { + if (FTB_TEAMS_LOADED && level != null) { + try { + return getTeamName(level, playerUUID); + } catch (Exception ignored) { + } + } + + // 尝试获取玩家名称 + if (level != null) { + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + } + + return Component.literal(playerUUID.toString()); + } + + /** + * 检查网络所有者是否有效(玩家在线或队伍存在) + * + * @param level 服务端世界 + * @param playerUUID 玩家UUID + * @return 是否有效 + */ + public static boolean hasNetworkOwner(@Nullable ServerLevel level, UUID playerUUID) { + if (FTB_TEAMS_LOADED && level != null) { + try { + return hasTeamOwner(level, playerUUID); + } catch (Exception ignored) { + } + } + + // 检查玩家是否在线 + if (level != null) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + return false; + } + + // ==================== FTBTeams 集成(通过反射调用避免硬依赖)==================== + + private static UUID getTeamUUID(ServerLevel level, UUID playerUUID) { + try { + // 使用FTBTeams API + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); // 静态方法,返回API实例 + + // 检查Manager是否已加载(在api实例上调用) + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + + if (!isLoaded) { + return playerUUID; + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return playerUUID; + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + var isPresent = (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + + if (isPresent) { + var team = optionalClass.getMethod("get").invoke(teamOptional); + var teamClass = team.getClass(); + return (UUID) teamClass.getMethod("getTeamId").invoke(team); + } + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + return playerUUID; + } + + private static Component getTeamName(ServerLevel level, UUID playerUUID) { + try { + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); + + // 检查Manager是否已加载 + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + if (!isLoaded) { + // Manager未加载,回退到玩家名称 + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + return Component.literal(playerUUID.toString()); + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return Component.literal(playerUUID.toString()); + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + var isPresent = (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + + if (isPresent) { + var team = optionalClass.getMethod("get").invoke(teamOptional); + var teamClass = team.getClass(); + return (Component) teamClass.getMethod("getName").invoke(team); + } + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + // 回退到玩家名称 + ServerPlayer player = level.getServer().getPlayerList().getPlayer(playerUUID); + if (player != null) { + return player.getName(); + } + + return Component.literal(playerUUID.toString()); + } + + private static boolean hasTeamOwner(ServerLevel level, UUID playerUUID) { + try { + var apiClass = Class.forName("dev.ftb.mods.ftbteams.api.FTBTeamsAPI"); + var api = apiClass.getMethod("api").invoke(null); + + // 检查Manager是否已加载 + Boolean isLoaded = (Boolean) api.getClass().getMethod("isManagerLoaded").invoke(api); + if (!isLoaded) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + var getManager = api.getClass().getMethod("getManager").invoke(api); + + if (getManager == null) { + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } + + var managerClass = getManager.getClass(); + var getTeamForPlayer = managerClass.getMethod("getTeamForPlayerID", UUID.class); + var teamOptional = getTeamForPlayer.invoke(getManager, playerUUID); + + if (teamOptional != null) { + var optionalClass = teamOptional.getClass(); + return (boolean) optionalClass.getMethod("isPresent").invoke(teamOptional); + } + } catch (Exception e) { + // 反射调用失败,回退 + } + + return level.getServer().getPlayerList().getPlayer(playerUUID) != null; + } +} + diff --git a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java b/src/main/java/com/extendedae_plus/util/command/InfinityDiskGiveCommand.java similarity index 98% rename from src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java rename to src/main/java/com/extendedae_plus/util/command/InfinityDiskGiveCommand.java index 67305f3..399eca1 100644 --- a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java +++ b/src/main/java/com/extendedae_plus/util/command/InfinityDiskGiveCommand.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.command; +package com.extendedae_plus.util.command; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; diff --git a/src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java b/src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java new file mode 100644 index 0000000..f34e020 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/entitySpeed/ConfigParsingUtils.java @@ -0,0 +1,224 @@ +package com.extendedae_plus.util.entitySpeed; + +import com.extendedae_plus.config.ModConfig; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import static com.extendedae_plus.util.Logger.EAP$LOGGER; + +/** + * 配置解析工具类:用于解析黑名单与倍率配置的字符串 + */ +public final class ConfigParsingUtils { + private ConfigParsingUtils() {} + + public record MultiplierEntry(Pattern pattern, double multiplier) { } + + // 直接引用配置 + private static final Supplier BLACKLIST_SUPPLIER = () -> ModConfig.INSTANCE.entityTickerBlackList; + private static final Supplier MULTIPLIERS_SUPPLIER = () -> ModConfig.INSTANCE.entityTickerMultipliers; + + // 缓存字段 + private static volatile Map cachedBlacklist = null; + private static volatile Map cachedMultiplierEntries = null; + private static volatile String[] cachedBlacklistSourceSnapshot = null; + private static volatile String[] cachedMultiplierSourceSnapshot = null; + private static volatile Map blacklistResultCache = new HashMap<>(); + private static volatile Map multiplierResultCache = new HashMap<>(); + private static final Object CACHE_LOCK = new Object(); + + /* + 初始化缓存,在模组加载时调用 + */ + static { + reload(); + } + + /** + * 编译用户提供的匹配串。支持简单的 glob 语法('*' 和 '?')以及完整的正则表达式。 + */ + public static Pattern compilePattern(String raw) { + if (raw == null || raw.trim().isEmpty()) { + EAP$LOGGER.warn("Invalid pattern: {}", raw); + throw new IllegalArgumentException("Pattern is null or empty"); + } + raw = raw.trim(); + + // Try regex first if it contains regex metacharacters + if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) { + try { + return Pattern.compile("^" + raw + "$"); + } catch (PatternSyntaxException e) { + EAP$LOGGER.warn("Failed to compile regex pattern '{}': {}", raw, e.getMessage()); + // Fallback to glob + } + } + + // Convert glob to regex + if (raw.contains("*") || raw.contains("?")) { + StringBuilder sb = new StringBuilder("^"); + for (char c : raw.toCharArray()) { + switch (c) { + case '*': sb.append(".*"); break; + case '?': sb.append('.'); break; + default: + if (".\\+[]{}()^$|".indexOf(c) >= 0) { + sb.append('\\'); + } + sb.append(c); + } + } + sb.append('$'); + return Pattern.compile(sb.toString()); + } + + // Literal match + return Pattern.compile("^" + Pattern.quote(raw) + "$"); + } + + /** + * 解析倍率条目,如 'modid:block 2x'。 + */ + public static MultiplierEntry parseMultiplierEntry(String entry) { + if (entry == null || entry.trim().isEmpty()) return null; + String[] parts = entry.trim().split("\\s+"); + if (parts.length < 2) { + EAP$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 multiplier; + try { + multiplier = Double.parseDouble(val); + } catch (NumberFormatException e) { + EAP$LOGGER.warn("Invalid multiplier value in '{}': {}", entry, val); + return null; + } + try { + Pattern pattern = compilePattern(key); + return new MultiplierEntry(pattern, multiplier); + } catch (IllegalArgumentException e) { + EAP$LOGGER.warn("Failed to compile pattern in '{}': {}", entry, e.getMessage()); + return null; + } + } + + /** + * 检查方块是否在黑名单中。 + */ + public static boolean isBlockBlacklisted(String blockId) { + if (blockId == null || BLACKLIST_SUPPLIER.get() == null || BLACKLIST_SUPPLIER.get().length == 0) { + return false; + } + return blacklistResultCache.computeIfAbsent(blockId, id -> + getCachedBlacklist().values().stream().anyMatch(p -> p.matcher(id).matches()) + ); + } + + /** + * 获取方块的倍率。 + */ + public static double getMultiplierForBlock(String blockId) { + if (blockId == null || MULTIPLIERS_SUPPLIER.get() == null || MULTIPLIERS_SUPPLIER.get().length == 0) { + return 1.0; + } + return multiplierResultCache.computeIfAbsent(blockId, id -> { + double maxMultiplier = 1.0; + for (MultiplierEntry me : getCachedMultiplierEntries().values()) { + if (me.pattern.matcher(id).matches()) { + maxMultiplier = Math.max(maxMultiplier, me.multiplier); + } + } + return maxMultiplier; + }); + } + + /** + * 编译黑名单配置为 Map。 + */ + public static Map compilePatterns(String[] raw) { + Map out = new HashMap<>(); + if (raw == null) return out; + for (String s : raw) { + if (s == null || s.trim().isEmpty()) continue; + String trimmed = s.trim(); + try { + out.put(trimmed, compilePattern(trimmed)); + } catch (IllegalArgumentException e) { + EAP$LOGGER.warn("Failed to compile pattern '{}': {}", trimmed, e.getMessage()); + } + } + return out; + } + + /** + * 解析倍率配置为 Map。 + */ + public static Map parseMultiplierList(String[] raw) { + Map out = new HashMap<>(); + if (raw == null) return out; + for (String s : raw) { + if (s == null || s.trim().isEmpty()) continue; + String trimmed = s.trim(); + MultiplierEntry me = parseMultiplierEntry(trimmed); + if (me != null) out.put(trimmed, me); + } + return out; + } + + /** + * 获取已解析并缓存的黑名单(线程安全、懒加载)。 + */ + private static Map getCachedBlacklist() { + String[] source = BLACKLIST_SUPPLIER.get(); + if (cachedBlacklist != null && Arrays.equals(cachedBlacklistSourceSnapshot, source)) { + return Collections.unmodifiableMap(cachedBlacklist); + } + synchronized (CACHE_LOCK) { + if (cachedBlacklist == null || !Arrays.equals(cachedBlacklistSourceSnapshot, source)) { + cachedBlacklist = compilePatterns(source); + cachedBlacklistSourceSnapshot = source == null ? new String[0] : source.clone(); + } + return Collections.unmodifiableMap(cachedBlacklist); + } + } + + /** + * 获取已解析并缓存的倍率列表(线程安全、懒加载)。 + */ + private static Map getCachedMultiplierEntries() { + String[] source = MULTIPLIERS_SUPPLIER.get(); + if (cachedMultiplierEntries != null && Arrays.equals(cachedMultiplierSourceSnapshot, source)) { + return Collections.unmodifiableMap(cachedMultiplierEntries); + } + synchronized (CACHE_LOCK) { + if (cachedMultiplierEntries == null || !Arrays.equals(cachedMultiplierSourceSnapshot, source)) { + cachedMultiplierEntries = parseMultiplierList(source); + cachedMultiplierSourceSnapshot = source == null ? new String[0] : source.clone(); + } + return Collections.unmodifiableMap(cachedMultiplierEntries); + } + } + + /** + * 清空缓存,下一次获取时将重新解析。 + */ + public static void reload() { + synchronized (CACHE_LOCK) { + cachedBlacklist = null; + cachedMultiplierEntries = null; + cachedBlacklistSourceSnapshot = null; + cachedMultiplierSourceSnapshot = null; + blacklistResultCache.clear(); + multiplierResultCache.clear(); + } + } +} \ 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..5115be4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/entitySpeed/PowerUtils.java @@ -0,0 +1,156 @@ +package com.extendedae_plus.util.entitySpeed; + +import appeng.api.upgrades.IUpgradeInventory; +import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem; +import com.extendedae_plus.config.ModConfig; +import net.minecraft.world.item.ItemStack; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 用于计算实体加速器的能耗与加速倍率的工具类 + */ +public final class PowerUtils { + private static final int[] VALID_MULTIPLIERS = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024}; + private static final int[] VALID_ENERGY_CARD_COUNTS = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + private static volatile Map> powerCache = new HashMap<>(); + private static volatile Map ratioCache = new HashMap<>(); + + // 静态初始化块,预计算缓存 + static { + initializeCaches(); + } + + private PowerUtils() {} + + /** + * 初始化所有可能的缓存条目 + */ + public static void initializeCaches() { + synchronized (PowerUtils.class) { + powerCache.clear(); + ratioCache.clear(); + for (int energyCardCount : VALID_ENERGY_CARD_COUNTS) { + double ratio = + energyCardCount == 0 ? 1.0 : + energyCardCount == 1 ? 0.9 : + energyCardCount == 8 ? 0.5 : + 1.0 - 0.5 * (1.0 - Math.pow(0.7, energyCardCount)); + ratioCache.put(energyCardCount, ratio); + } + for (int product : VALID_MULTIPLIERS) { + Map energyCardMap = new HashMap<>(); + for (int energyCardCount : VALID_ENERGY_CARD_COUNTS) { + double power = computePowerForProduct(product, energyCardCount); + energyCardMap.put(energyCardCount, power); + } + powerCache.put(product, energyCardMap); + } + } + } + + /** + * 计算指定倍率和能源卡数量的能耗 + */ + private static double computePowerForProduct(int product, int energyCardCount) { + double base = ModConfig.INSTANCE.entityTickerCost; + int log2 = Integer.numberOfTrailingZeros(product); + double raw; + if (product == 2) { + raw = base * 4; + } else if (product <= 256) { + raw = base * (1L << (int)(1.5 * log2)) * 2; + } else { + raw = base * (1L << (int)(2.5 * log2)); + } + double ratio = + energyCardCount <= 0 ? 1.0 : + energyCardCount == 1 ? 0.9 : + energyCardCount >= 8 ? 0.5 : + 1.0 - 0.5 * (1.0 - Math.pow(0.7, energyCardCount)); + return raw * ratio / 8.0; + } + + /** + * 计算加速卡的乘积并应用上限。 + * + * @param upgrades 升级槽位 + * @param maxCards 最大计入的卡数 + * @return 被上限约束后的乘积 + */ + public static int 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++; + } + } + } + int product = 1; + 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 int capForHighestMultiplier(int highestMultiplier) { + if (highestMultiplier >= 16) return 1024; + if (highestMultiplier >= 8) return 256; + if (highestMultiplier >= 4) return 64; + if (highestMultiplier >= 2) return 8; + return 1; + } + + /** + * 直接从缓存获取能耗值 + * + * @param product 加速卡乘积 + * @param energyCardCount 能量卡数量 + * @return 缓存的能耗值 + */ + public static double getCachedPower(int product, int energyCardCount) { + if (product <= 1) return 0.0; + Map energyCardMap = powerCache.get(product); + if (energyCardMap == null) return 0.0; + Double cachedPower = energyCardMap.get(energyCardCount); + if (cachedPower == null) return 0.0; + return cachedPower; + } + + /** + * 直接从缓存获取比率(无配置检查)。 + * + * @param energyCardCount 能量卡数量 + * @return 缓存的比率值 + */ + public static double getCachedRatio(int energyCardCount) { + Double cachedRatio = ratioCache.get(energyCardCount); + if (cachedRatio == null) return 1.0; + return cachedRatio; + } + + /** + * 将剩余功耗比率格式化为百分比字符串。 + */ + 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/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java b/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java new file mode 100644 index 0000000..5f4fcca --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java @@ -0,0 +1,155 @@ +package com.extendedae_plus.util.smartDoubling; + +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.crafting.pattern.AEProcessingPattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern; +import com.extendedae_plus.ae.api.crafting.ScaledProcessingPatternAdv; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; +import com.extendedae_plus.config.ModConfig; +import net.minecraftforge.fml.ModList; + +public final class PatternScaler { + private PatternScaler() { + } + + public static ScaledProcessingPattern scale(AEProcessingPattern base, AEKey target, long requestedAmount) { + if (base == null) throw new IllegalArgumentException("base"); + if (target == null) throw new IllegalArgumentException("target"); + + // 双保险:若样板标记为不允许缩放,直接放弃缩放(返回 null 表示调用方应保持原样板) + if (base instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { + return null; + } + + GenericStack[] baseOutputs = base.getOutputs(); + + // 新逻辑:不再对样板进行单位化处理 + // 找到目标输出在 outputs 中的索引(尝试匹配 target,否则取第一个非空输出) + int targetOutIndex = -1; + for (int i = 0; i < baseOutputs.length; i++) { + var out = baseOutputs[i]; + if (out != null && target != null && out.what() != null && out.what().equals(target)) { + targetOutIndex = i; + break; + } + } + if (targetOutIndex == -1) { + for (int i = 0; i < baseOutputs.length; i++) { + if (baseOutputs[i] != null) { + targetOutIndex = i; + break; + } + } + } + if (targetOutIndex == -1 && baseOutputs.length > 0) targetOutIndex = 0; + + long perOperationTarget = 1L; + if (targetOutIndex >= 0 && baseOutputs[targetOutIndex] != null) { + long amt = baseOutputs[targetOutIndex].amount(); + if (amt > 0) perOperationTarget = amt; + } + + long multiplier = 1L; // 默认倍数 + + // ---------------------- 优先模式限制 ---------------------- + boolean patternHasLimit = false; + if (base instanceof ISmartDoublingAwarePattern aware) { + int patternMulLimit = aware.eap$getMultiplierLimit(); + if (patternMulLimit > 0) { + multiplier = patternMulLimit; // 直接使用模式限制作为倍数 + patternHasLimit = true; + } + } + + // ---------------------- 全局逻辑(仅在没有模式限制时生效) ---------------------- + if (!patternHasLimit && requestedAmount > 0) { + // 计算满足请求量的最小倍数 + long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1); + multiplier = Math.max(needed, 1L); + + // 应用全局上限 + int maxMul = ModConfig.INSTANCE.smartScalingMaxMultiplier; + if (maxMul > 0 && multiplier > maxMul) multiplier = maxMul; + } + + // ---------------------- 小请求绕过 ---------------------- + try { + int minBenefit = ModConfig.INSTANCE.smartScalingMinBenefitFactor; + if (minBenefit > 1 && requestedAmount > 0 && requestedAmount < perOperationTarget * (long) minBenefit) { + return null; + } + } catch (Throwable ignore) {} + + if (ModList.get().isLoaded("advanced_ae")) { + // 如果加载了 Advanced AE 且 base 实现了 AdvPatternDetails,返回兼容版 + try { + // 软依赖,不直接 import advIface + Class advIface = Class.forName("net.pedroksl.advanced_ae.common.patterns.AdvPatternDetails"); + if (advIface.isInstance(base)) { + // 直接 new ScaledProcessingPatternAdv,父类字段会正常初始化 + return new ScaledProcessingPatternAdv(base, base.getDefinition(), multiplier); + } + } catch (Throwable ignore) { + // 如果 Advanced AE 不存在或反射失败,就忽略,继续走普通逻辑 + } + } + // 仅使用 multiplier 构建轻量化 ScaledProcessingPattern(具体视图按需计算) + return new ScaledProcessingPattern(base, base.getDefinition(), multiplier); + } + + public static int getComputedMul(AEProcessingPattern proc, int limit) { + int computedMul = 0; // 默认 0 表示不限制 + + if (limit > 0) { + long minMul = Long.MAX_VALUE; + + for (var input : proc.getInputs()) { + long amt = input.getMultiplier(); + if (amt <= 0) continue; + + AEKey key = input.getPossibleInputs()[0].what(); + + // 使用统一单位换算 + long unitMultiplier = getUnitMultiplier(key); + long limitInAEUnit = limit * unitMultiplier; + + // 计算该输入允许的倍数 + long allowedMul = limitInAEUnit / amt; + + // 保证至少为 1 + allowedMul = Math.max(1, allowedMul); + + // 取最小值,保证所有输入都不超过 limit + minMul = Math.min(minMul, allowedMul); + } + + if (minMul != Long.MAX_VALUE) { + computedMul = (int) Math.min(minMul, Integer.MAX_VALUE); + } + } + + return computedMul; + } + + /** + * 获取 AEKey 的单位换算系数 + * 物品默认 1,流体默认 1000,其它类型可通过扩展接口提供 + */ + private static long getUnitMultiplier(AEKey key) { + if (key instanceof AEItemKey) return 1L; + if (key instanceof AEFluidKey) return 1000L; + + try { + // 反射判断扩展 Key 类型 + if (key.getClass().getName().equals("me.ramidzkh.mekae2.ae2.MekanismKey")) { + return 1000L; + } + // 根据需要继续增加 + } catch (Exception ignored) {} + + return 1L; // 默认单位 + } +} diff --git a/src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java b/src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java similarity index 95% rename from src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java rename to src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java index e70187a..005f919 100644 --- a/src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java +++ b/src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java @@ -1,4 +1,4 @@ -package com.extendedae_plus.util; +package com.extendedae_plus.util.smartDoubling; import java.util.ArrayDeque; import java.util.Deque; diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 47b794c..4e3390f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -8,8 +8,8 @@ modId = "extendedae_plus" version = "${version}" displayName = "ExtendedAE Plus" authors = "GaLi" -description = '为extendedAE模组增强更多实用功能' -#logoFile = "" +description = "Add more practical features and auxiliary operations to the Applied Energistics 2 mod" +logoFile = "extendedae_plus.png" [[dependencies.extendedae_plus]] modId = "forge" 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 9d88bc6..44be39c 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -27,6 +27,12 @@ "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", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip": "Assembler Matrix Upload Core", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload": "Upload to Assembler Matrix", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_success": "Pattern uploaded to Assembler Matrix", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "Only crafting patterns supported, processing patterns ignored", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "No formed Assembler Matrix found in network", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "Assembler Matrix pattern storage full or cannot insert", "block.extendedae_plus.wireless_transceiver": "Wireless Transceiver", "block.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator", @@ -66,9 +72,13 @@ "config.jade.plugin_extendedae_plus.wt_master_location": "Show Master Position", "config.jade.plugin_extendedae_plus.wt_locked": "Show Locked State", "config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State", + "config.jade.plugin_extendedae_plus.wt_channels": "Show Channel Usage", + "config.jade.plugin_extendedae_plus.wt_owner": "Show Owner Information", "extendedae_plus.tooltip.frequency": "Frequency: %d", "extendedae_plus.tooltip.master_mode": "Mode: %s", "extendedae_plus.tooltip.locked": "Locked: %s", + "extendedae_plus.tooltip.channels": "Channels: %d", + "extendedae_plus.tooltip.channels_of": "Channels: %d / %d", "config.screen.extendedae_plus": "ExtendedAE Plus Config", "config.extendedae_plus.option.pageMultiplier": "Pattern Provider Page Multiplier", @@ -88,11 +98,28 @@ "config.extendedae_plus.option.entityTickerBlackList": "Entity Ticker Blacklist", "config.extendedae_plus.option.entityTickerMultipliers": "Entity Ticker Extra Consumption Multipliers", "config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold", - "block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core", + "config.extendedae_plus.option.prioritizeDiskEnergy": "Prioritize FE energy from disk (requires Applied Flux)", "item.extendedae_plus.channel_card": "Channel Card", "item.extendedae_plus.channel_card.channel": "Frequency: %s", "item.extendedae_plus.channel_card.channel.unset": "Frequency: Unset", "item.extendedae_plus.channel_card.set": "Frequency set to: %s", + "item.extendedae_plus.channel_card.owner.unset": "Owner: Unbound", + "item.extendedae_plus.channel_card.owner.team": "Owner: %s", + "item.extendedae_plus.channel_card.owner.player": "Owner: Player %s", + "item.extendedae_plus.channel_card.owner.bound": "Bound to: %s", + "item.extendedae_plus.channel_card.owner.cleared": "Owner binding cleared", + + "extendedae_plus.tooltip.owner": "Owner: %s", + "extendedae_plus.tooltip.owner.unset": "Owner: Unset", + "extendedae_plus.tooltip.owner.public": "Owner: Public", + + "extendedae_plus.screen.frequency_input.title": "Set Frequency", + "extendedae_plus.screen.frequency_input.current": "Current Frequency: %s", + "extendedae_plus.screen.frequency_input.field": "Frequency", + "extendedae_plus.screen.frequency_input.confirm": "Confirm", + + "group.pattern_provider.name": "Pattern Provider", + "group.entity_ticker.name": "Entity Accelerator", "group.storage.name": "StorageBus" } \ No newline at end of file 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 076e6aa..47df18b 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -18,7 +18,6 @@ "item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元", "item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器", "item.extendedae_plus.entity_speed_card": "实体加速卡", - "item.extendedae_plus.entity_speed_card.x1": "§k§l?#@§r§8不§k§l1§r限§k§l0§r存于§k§l%§r现实的§c实§k&0%§r体加§k§l*§r速卡§k§l...§r", "item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)", "item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)", "item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)", @@ -28,6 +27,12 @@ "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 倍", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip": "装配矩阵上传核心", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload": "上传到装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_success": "样板已上传到装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "未在当前网络中找到已成型的装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "装配矩阵的样板仓已满或无法插入", "block.extendedae_plus.wireless_transceiver": "无线收发器", "block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元", @@ -67,9 +72,13 @@ "config.jade.plugin_extendedae_plus.wt_master_location": "显示主节点位置", "config.jade.plugin_extendedae_plus.wt_locked": "显示锁定状态", "config.jade.plugin_extendedae_plus.wt_network_usable": "显示网络在线状态", + "config.jade.plugin_extendedae_plus.wt_channels": "显示频道使用情况", + "config.jade.plugin_extendedae_plus.wt_owner": "显示所有者信息", "extendedae_plus.tooltip.frequency": "频率: %d", "extendedae_plus.tooltip.master_mode": "模式: %s", "extendedae_plus.tooltip.locked": "锁定状态: %s", + "extendedae_plus.tooltip.channels": "频道: %d", + "extendedae_plus.tooltip.channels_of": "频道: %d / %d", "config.screen.extendedae_plus": "ExtendedAE Plus 配置", "config.extendedae_plus.option.pageMultiplier": "扩展样板供应器槽位倍率", @@ -88,6 +97,7 @@ "config.extendedae_plus.option.entityTickerCost": "实体加速器能量消耗基础值", "config.extendedae_plus.option.entityTickerBlackList": "实体加速器黑名单", "config.extendedae_plus.option.entityTickerMultipliers": "实体加速器额外消耗倍率", + "config.extendedae_plus.option.prioritizeDiskEnergy": "优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)", "config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值", "config.extendedae_plus.option.restrictCraftingPatternToMolecular": "限制合成样板自动上传仅进入分子操纵者", @@ -104,6 +114,21 @@ "item.extendedae_plus.channel_card.channel": "频率:%s", "item.extendedae_plus.channel_card.channel.unset": "频率:未设置", "item.extendedae_plus.channel_card.set": "已设置频率:%s", + "item.extendedae_plus.channel_card.owner.unset": "所有者:未绑定", + "item.extendedae_plus.channel_card.owner.team": "所有者:%s", + "item.extendedae_plus.channel_card.owner.player": "所有者:玩家 %s", + "item.extendedae_plus.channel_card.owner.bound": "已绑定至:%s", + "item.extendedae_plus.channel_card.owner.cleared": "已清除所有者绑定", + + "extendedae_plus.tooltip.owner": "所有者: %s", + "extendedae_plus.tooltip.owner.unset": "所有者: 未设置", + "extendedae_plus.tooltip.owner.public": "所有者: 公共", + + "extendedae_plus.screen.frequency_input.title": "设置频率", + "extendedae_plus.screen.frequency_input.current": "当前频率:%s", + "extendedae_plus.screen.frequency_input.field": "频率", + "extendedae_plus.screen.frequency_input.confirm": "确认", + "group.pattern_provider.name": "样板供应器", "group.entity_ticker.name": "实体加速器", "group.storage.name": "存储总线" diff --git a/src/main/resources/data/extendedae_plus/loot_tables/blocks/assembler_matrix_upload_core.json b/src/main/resources/data/extendedae_plus/loot_tables/blocks/assembler_matrix_upload_core.json new file mode 100644 index 0000000..b9f06ad --- /dev/null +++ b/src/main/resources/data/extendedae_plus/loot_tables/blocks/assembler_matrix_upload_core.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "extendedae_plus:assembler_matrix_upload_core" + } + ], + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ] + } + ] +} diff --git a/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json b/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json index 388e10b..bcae23e 100644 --- a/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json +++ b/src/main/resources/data/minecraft/tags/blocks/mineable/pickaxe.json @@ -3,6 +3,7 @@ "values": [ "extendedae_plus:network_pattern_controller", "extendedae_plus:wireless_transceiver", + "extendedae_plus:assembler_matrix_upload_core", "extendedae_plus:4x_crafting_accelerator", "extendedae_plus:16x_crafting_accelerator", "extendedae_plus:64x_crafting_accelerator", diff --git a/src/main/resources/data/minecraft/tags/blocks/needs_stone_tool.json b/src/main/resources/data/minecraft/tags/blocks/needs_stone_tool.json index a01207f..26c3fbb 100644 --- a/src/main/resources/data/minecraft/tags/blocks/needs_stone_tool.json +++ b/src/main/resources/data/minecraft/tags/blocks/needs_stone_tool.json @@ -2,6 +2,7 @@ "replace": false, "values": [ "extendedae_plus:wireless_transceiver", + "extendedae_plus:assembler_matrix_upload_core", "extendedae_plus:4x_crafting_accelerator", "extendedae_plus:16x_crafting_accelerator", "extendedae_plus:64x_crafting_accelerator", diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index cb629e0..92c9229 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -9,9 +9,7 @@ "accessor.ScreenAccessor", "advancedae.client.gui.AdvPatternProviderScreenMixin", "advancedae.client.gui.SmallAdvPatternProviderScreenMixin", - "ae2.QuartzCuttingKnifeItemMixin", "ae2.accessor.AEBaseScreenAccessor", - "ae2.accessor.AEBaseScreenInvoker", "ae2.accessor.MEStorageScreenAccessor", "ae2.accessor.PatternAccessTermScreenAccessor", "ae2.accessor.PatternAccessTermScreenSlotsRowAccessor", @@ -22,6 +20,8 @@ "ae2.client.gui.PatternProviderScreenMixin", "ae2.client.gui.SlotGridLayoutMixin", "ae2.compat.PatternProviderScreenCompatMixin", + "ae2.items.QuartzCuttingKnifeItemMixin", + "ae2.compat.PatternProviderScreenCompatMixin", "ae2.menu.CraftConfirmMenuGoBackMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", @@ -51,14 +51,22 @@ "ae2.accessor.MEStorageMenuAccessor", "ae2.accessor.PatternEncodingTermMenuAccessor", "ae2.accessor.PatternProviderLogicAccessor", - "ae2.accessor.PatternProviderLogicPatternInputsAccessor", - "ae2.accessor.PatternProviderLogicPatternsAccessor", - "ae2.accessor.PatternProviderMenuAdvancedAccessor", + "ae2.accessor.PatternProviderMenuAccessor", "ae2.autopattern.CraftingServiceGetProvidersMixin", "ae2.autopattern.CraftingTreeNodeAccessor", "ae2.autopattern.CraftingTreeNodeMixin", "ae2.autopattern.CraftingTreeProcessMixin", "ae2.autopattern.PatternProviderLogicContainsRedirectMixin", + "ae2.compat.PatternProviderCompatMixin", + "ae2.compat.PatternProviderLogicCompatMixin", + "ae2.compat.PatternProviderLogicHostCompatMixin", + "ae2.helpers.InterfaceLogicChannelCardMixin", + "ae2.helpers.InterfaceLogicTickerMixin", + "ae2.helpers.InterfaceLogicUpgradesMixin", + "ae2.helpers.patternprovider.PatternProviderLogicAdvancedMixin", + "ae2.helpers.patternprovider.PatternProviderLogicDoublingMixin", + "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", + "ae2.items.MemoryCardItemMixin", "ae2.autopattern.gtceu.GTLCoreMEPatternBufferPartMachineMixin", "ae2.autopattern.gtceu.MEPatternBufferPartMachineMixin", "ae2.compat.PatternProviderCompatMixin", diff --git a/src/main/resources/extendedae_plus.png b/src/main/resources/extendedae_plus.png new file mode 100644 index 0000000..842eabe Binary files /dev/null and b/src/main/resources/extendedae_plus.png differ