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

# Conflicts:
#	build.gradle
#	gradle.properties
#	src/main/java/com/extendedae_plus/config/ModConfig.java
#	src/main/java/com/extendedae_plus/network/CraftingMonitorOpenProviderC2SPacket.java
#	src/main/resources/assets/extendedae_plus/lang/zh_cn.json
#	src/main/resources/extendedae_plus.mixins.json
This commit is contained in:
C-H716 2025-10-18 20:34:43 +08:00
commit e5174f0bd3
136 changed files with 4192 additions and 1844 deletions

2
.gitignore vendored
View File

@ -4,7 +4,7 @@ build/
out/ out/
classes/ classes/
source/ source/
Applied-Energistics-2-forge-1.20.1/
# Eclipse # Eclipse
*.tmp *.tmp
*.bak *.bak

View File

@ -2,6 +2,29 @@
## [Unreleased] ## [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] ## [1.4.3]
### ⚠️ 重要提醒 / Important Notice ### ⚠️ 重要提醒 / Important Notice
- **更新会导致旧版的无限盘失效,如有需要请先导盘再更新** - **更新会导致旧版的无限盘失效,如有需要请先导盘再更新**

View File

@ -72,6 +72,7 @@ dependencies {
//extendedAE //extendedAE
modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar') modImplementation files('libs/ExtendedAE-1.20-1.4.2-forge.jar')
modRuntimeOnly fileTree(dir: 'libs', includes: ['*.jar']) modRuntimeOnly fileTree(dir: 'libs', includes: ['*.jar'])
//ae2 //ae2
modImplementation "appeng:appliedenergistics2-forge:${ae2_version}" modImplementation "appeng:appliedenergistics2-forge:${ae2_version}"
modImplementation "org.appliedenergistics:guideme:${guideme_version}" modImplementation "org.appliedenergistics:guideme:${guideme_version}"
@ -121,12 +122,16 @@ dependencies {
// compileOnly // compileOnly
include "curse.maven:configuration-444699:4710266" include "curse.maven:configuration-444699:4710266"
modImplementation "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 { compileJava {
tasks.withType(JavaCompile).configureEach { options.compilerArgs += ['-Xlint:-deprecation', '-Xlint:-removal', '-Xlint:-unchecked']
options.compilerArgs << "-Xlint:-deprecation"
}
} }
processResources { processResources {

View File

@ -4,10 +4,10 @@ import appeng.api.storage.StorageCells;
import appeng.menu.locator.MenuLocators; import appeng.menu.locator.MenuLocators;
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
import com.extendedae_plus.client.ClientRegistrar; import com.extendedae_plus.client.ClientRegistrar;
import com.extendedae_plus.command.InfinityDiskGiveCommand;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.init.*; import com.extendedae_plus.init.*;
import com.extendedae_plus.menu.locator.CuriosItemLocator; import com.extendedae_plus.menu.locator.CuriosItemLocator;
import com.extendedae_plus.util.command.InfinityDiskGiveCommand;
import com.extendedae_plus.util.storage.InfinityStorageManager; import com.extendedae_plus.util.storage.InfinityStorageManager;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;

View File

@ -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<String, Setting<?>> SETTINGS = new HashMap<>();
public static final Setting<YesNo> ACCELERATE = register("accelerate", YesNo.NO, YesNo.YES);
private Settings() {
}
private synchronized static <T extends Enum<T>> Setting<T> register(String name, Class<T> enumClass) {
Preconditions.checkState(!SETTINGS.containsKey(name));
var setting = new Setting<>(name, enumClass);
SETTINGS.put(name, setting);
return setting;
}
@SafeVarargs
private synchronized static <T extends Enum<T>> Setting<T> register(String name, T firstOption, T... moreOptions) {
Preconditions.checkState(!SETTINGS.containsKey(name));
var setting = new Setting<T>(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;
}
}

View File

@ -1,4 +1,4 @@
package com.extendedae_plus.content; package com.extendedae_plus.ae.api.crafting;
import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails;
import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEItemKey;
@ -15,7 +15,7 @@ import java.util.Objects;
* 缩放后的处理样板结构完全模拟 AEProcessingPattern * 缩放后的处理样板结构完全模拟 AEProcessingPattern
* 保持 sparse/condensed/inputs 的一致性同时保存原始样板 * 保持 sparse/condensed/inputs 的一致性同时保存原始样板
*/ */
public final class ScaledProcessingPattern implements IPatternDetails { public class ScaledProcessingPattern implements IPatternDetails {
// 最小化实例字段只保留原始样板引用定义和倍数 // 最小化实例字段只保留原始样板引用定义和倍数
private final AEProcessingPattern original; // 原始样板引用 private final AEProcessingPattern original; // 原始样板引用

View File

@ -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<AEKey, Direction> 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);
}
}
}
}

View File

@ -326,8 +326,11 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
if (amount == 0){ if (amount == 0){
return 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; return 0;
} }

View File

@ -1,6 +1,7 @@
package com.extendedae_plus; package com.extendedae_plus.ae.client.gui;
import appeng.client.gui.style.Blitter; import appeng.client.gui.style.Blitter;
import com.extendedae_plus.ExtendedAEPlus;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
public class NewIcon { public class NewIcon {

View File

@ -1,8 +1,10 @@
package com.extendedae_plus.ae.items; package com.extendedae_plus.ae.items;
import appeng.items.materials.UpgradeCardItem; import appeng.items.materials.UpgradeCardItem;
import com.extendedae_plus.util.WirelessTeamUtil;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.TooltipFlag;
@ -13,13 +15,19 @@ import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.UUID;
/** /**
* 频道卡MVP仅存储一个 long 类型的频道号到 NBT"channel" * 频道卡存储频道号所有者UUID和团队信息
* - 右键空气增加频道号
* - 潜行左键空气写入/清除玩家UUID和团队信息
* - 潜行左键收发器将频道卡的所有者信息写入收发器
* 继承 AE2 UpgradeCardItem 以复用升级卡判定与提示框架 * 继承 AE2 UpgradeCardItem 以复用升级卡判定与提示框架
*/ */
public class ChannelCardItem extends UpgradeCardItem { public class ChannelCardItem extends UpgradeCardItem {
public static final String TAG_CHANNEL = "channel"; 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) { public ChannelCardItem(Item.Properties properties) {
super(properties); super(properties);
@ -35,29 +43,157 @@ public class ChannelCardItem extends UpgradeCardItem {
return tag != null && tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L; 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 @Override
public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> lines, TooltipFlag flag) { public void appendHoverText(ItemStack stack, @Nullable Level level, List<Component> lines, TooltipFlag flag) {
super.appendHoverText(stack, level, lines, flag); super.appendHoverText(stack, level, lines, flag);
// 显示频道
long ch = getChannel(stack); long ch = getChannel(stack);
if (ch == 0L) { if (ch == 0L) {
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset")); lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset"));
} else { } else {
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch)); 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 @Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) { public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand); ItemStack stack = player.getItemInHand(hand);
if (!level.isClientSide) { if (!level.isClientSide) {
long ch = getChannel(stack); long ch = getChannel(stack);
boolean dec = player.isShiftKeyDown(); long next;
long next = dec ? Math.max(0L, ch - 1L) : ch + 1L;
if (player.isShiftKeyDown()) {
// 潜行右键减少频率
next = Math.max(0L, ch - 1L);
} else {
// 普通右键增加频率
next = ch + 1L;
}
if (next != ch) { if (next != ch) {
setChannel(stack, next); setChannel(stack, next);
player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true); player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true);
} }
} }
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide); 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; // 拦截防止破坏方块
}
} }

View File

@ -17,8 +17,10 @@ import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem { 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)) { if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) {
try { try {
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL); 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); String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add( tooltip.add(
Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) 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)) { } else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) {
try { try {
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT); 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); String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
tooltip.add( tooltip.add(
Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) 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 * 创建一个带有指定 UUID Infinity 磁盘 ItemStack
*/ */
public static ItemStack withUUID(java.util.UUID uuid) { public static ItemStack withUUID(UUID uuid) {
ItemStack stack = new ItemStack(Objects.requireNonNull( ItemStack stack = new ItemStack(Objects.requireNonNull(
ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell") ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell")
))); )));

View File

@ -6,152 +6,169 @@ import appeng.menu.implementations.UpgradeableMenu;
import appeng.menu.slot.OptionalFakeSlot; import appeng.menu.slot.OptionalFakeSlot;
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart; import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen; import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes; import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.util.ConfigParsingUtils; import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils;
import com.extendedae_plus.util.PowerUtils; import com.extendedae_plus.util.entitySpeed.PowerUtils;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
import java.util.List; /**
* 实体加速器菜单负责管理客户端与服务端的数据同步处理加速卡能量卡和目标方块的状态
// 实体加速器菜单负责与客户端界面同步数据 */
public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart> { public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart> {
@GuiSync(716) public boolean accelerateEnabled = true; @GuiSync(716) public boolean accelerateEnabled = true; // 是否启用加速
// 已安装的实体加速卡数量用于能耗计算 @GuiSync(717) public int entitySpeedCardCount; // 已安装的实体加速卡数量
@GuiSync(717) public int entitySpeedCardCount; @GuiSync(718) public int energyCardCount; // 已安装的能量卡数量
// 已安装的能量卡数量 @GuiSync(719) public int effectiveSpeed = 1; // 当前生效的加速倍率
@GuiSync(718) public int energyCardCount; @GuiSync(720) public double multiplier = 1.0; // 目标方块的配置倍率
// 当前生效的配置倍率从配置中读取并同步 @GuiSync(721) public boolean targetBlacklisted = false; // 目标方块是否在黑名单中
// 当前计算出的生效速度product of multipliers同步给客户端用于显示 @GuiSync(722) public boolean networkEnergySufficient = true; // 网络能量是否充足
@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() { public boolean getAccelerateEnabled() {
return this.accelerateEnabled; return this.accelerateEnabled;
} }
/**
* 设置加速开关状态并同步到部件
* @param enabled 是否启用加速
*/
public void setAccelerateEnabled(boolean enabled) { public void setAccelerateEnabled(boolean enabled) {
this.accelerateEnabled = enabled; this.accelerateEnabled = enabled;
if (getHost() != null) {
getHost().setAccelerateEnabled(enabled); // 同步到部件
}
broadcastChanges(); // 广播状态变化
} }
/** /**
* Part 更新 networkEnergySufficient 的封装方法由服务器调用 * 更新网络能量充足状态并广播到客户端
* 该方法会更新 @GuiSync 字段并广播变化到客户端 * @param sufficient 是否能量充足
*/ */
public void setNetworkEnergySufficient(boolean sufficient) { public void setNetworkEnergySufficient(boolean sufficient) {
this.networkEnergySufficient = sufficient; this.networkEnergySufficient = sufficient;
// 触发一次数据广播使客户端立即接收到最新状态 broadcastChanges();
this.broadcastChanges();
} }
/**
// 构造方法初始化菜单并与部件绑定 * 服务端数据同步到客户端时调用更新卡数量目标状态和生效速度
public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) { */
super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host);
// 让部件持有当前菜单实例便于通信
getHost().menu = this;
// 初始同步部件上的开关状态到菜单服务器端构造时保证一致
try {
this.accelerateEnabled = getHost().getAccelerateEnabled();
} catch (Exception ignored) {}
}
// 当服务器数据同步到客户端时调用
@Override @Override
public void onServerDataSync() { public void onServerDataSync() {
super.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.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD); this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
}
// 计算当前面向方块的倍率服务器端并同步给客户端 /**
double mult = 1.0; * 更新目标方块的黑名单状态和倍率
try { */
BlockEntity target = getHost().getLevel().getBlockEntity(getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())); private void updateTargetStatus() {
if (target != null) { BlockEntity target = getTargetBlockEntity();
String blockId = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString(); if (target == null) {
for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(List.of(ModConfig.INSTANCE.entityTickerMultipliers))) { this.multiplier = 1.0;
if (me.pattern.matcher(blockId).matches()) { this.targetBlacklisted = false;
mult = Math.max(mult, me.multiplier); return;
}
}
}
} 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);
} }
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()) { private void updateEffectiveSpeed() {
this.networkEnergySufficient = host.isNetworkEnergySufficient(); this.effectiveSpeed = targetBlacklisted ? 0 : (int) PowerUtils.computeProductWithCap(getUpgrades(), 8);
} }
} catch (Exception ignored) {}
// 如果在客户端刷新界面 /**
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); private void refreshClientGui() {
// 客户端重新统计卡数量并刷新界面 if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
if (isClientSide()) { screen.refreshGui();
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();
}
} }
} }
@Override /**
public void broadcastChanges(){ * 获取目标方块实体
// 遍历所有槽位清理未启用但有物品显示的 OptionalFakeSlot * @return 目标方块实体或 null
for (Object o : this.slots) { */
if (o instanceof OptionalFakeSlot fs) { private BlockEntity getTargetBlockEntity() {
if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) { return getHost() != null ?
fs.clearStack(); getHost().getLevel().getBlockEntity(
} getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
} ) : null;
}
// 调用标准的同步方法通知监听者数据已更新
this.standardDetectAndSendChanges();
} }
} }

View File

@ -2,14 +2,18 @@ package com.extendedae_plus.ae.parts;
import appeng.api.config.Actionable; import appeng.api.config.Actionable;
import appeng.api.config.PowerMultiplier; import appeng.api.config.PowerMultiplier;
import appeng.api.config.YesNo;
import appeng.api.networking.GridFlags; import appeng.api.networking.GridFlags;
import appeng.api.networking.IGridNode; 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.IGridTickable;
import appeng.api.networking.ticking.TickRateModulation; import appeng.api.networking.ticking.TickRateModulation;
import appeng.api.networking.ticking.TickingRequest; import appeng.api.networking.ticking.TickingRequest;
import appeng.api.parts.IPartCollisionHelper; import appeng.api.parts.IPartCollisionHelper;
import appeng.api.parts.IPartItem; import appeng.api.parts.IPartItem;
import appeng.api.parts.IPartModel; import appeng.api.parts.IPartModel;
import appeng.api.storage.MEStorage;
import appeng.api.upgrades.IUpgradeableObject; import appeng.api.upgrades.IUpgradeableObject;
import appeng.core.definitions.AEItems; import appeng.core.definitions.AEItems;
import appeng.items.parts.PartModels; 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.config.ModConfig;
import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.init.ModMenuTypes; import com.extendedae_plus.init.ModMenuTypes;
import com.extendedae_plus.util.ConfigParsingUtils; import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils;
import com.extendedae_plus.util.PowerUtils; import com.extendedae_plus.util.entitySpeed.PowerUtils;
import com.extendedae_plus.util.Logger;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; 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.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.lang.reflect.Method;
import java.util.Objects;
import java.util.regex.Pattern;
/** /**
* EntitySpeedTickerPart 是一个可升级的 AE2 部件<p> * 实体加速器部件用于加速目标方块实体的 tick 速率消耗 AE 网络能量支持加速卡和能量卡升级
* 该部件可以加速目标方块实体的 tick 速率消耗 AE 网络能量并支持加速卡升级<p> * 灵感来源于 <a href="https://github.com/GilbertzRivi/crazyae2addons">Crazy AE2 Addons</a>
* 功能受<a href="https://github.com/GilbertzRivi/crazyae2addons">Crazy AE2 Addons</a>启发
*/ */
public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject { public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject {
// 当前打开的菜单实例如果有
public EntitySpeedTickerMenu menu;
public static final ResourceLocation MODEL_BASE = new ResourceLocation( public static final ResourceLocation MODEL_BASE = new ResourceLocation(
ExtendedAEPlus.MODID, "part/entity_speed_ticker_part"); ExtendedAEPlus.MODID, "part/entity_speed_ticker_part");
@PartModels @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 @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 @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 { static {
MODELS_OFF = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off")); cachedAttempts = ModConfig.INSTANCE.prioritizeDiskEnergy ? new boolean[]{true, false, true} : new boolean[]{false, true, true};
MODELS_ON = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on")); if (ModList.get().isLoaded("appflux")) {
MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel")); 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 部件物品 * @param partItem 部件物品
*/ */
public EntitySpeedTickerPart(IPartItem<?> partItem) { public EntitySpeedTickerPart(IPartItem<?> partItem) {
super(partItem); super(partItem);
// 设置网络节点属性需要通道空闲功耗为1并注册为 IGridTickable 服务
this.getMainNode() this.getMainNode()
.setFlags(GridFlags.REQUIRE_CHANNEL) .setFlags(GridFlags.REQUIRE_CHANNEL)
.setIdlePowerUsage(1) .setIdlePowerUsage(1)
.addService(IGridTickable.class, this); .addService(IGridTickable.class, this);
// 注册可记忆的配置YES/NO
this.getConfigManager().registerSetting(
com.extendedae_plus.ae.api.config.Settings.ACCELERATE,
YesNo.YES
);
} }
// 控制是否启用加速默认启用 /**
private boolean accelerateEnabled = true; * 更新缓存的 attempts 数组 ModConfig 调用
// 标记网络中能量是否充足用于 GUI 提示默认充足 */
private boolean networkEnergySufficient = true; public static void updateCachedAttempts(boolean prioritizeDiskEnergy) {
synchronized (EntitySpeedTickerPart.class) {
cachedAttempts = prioritizeDiskEnergy ? new boolean[]{true, false, true} : new boolean[]{false, true, true};
}
}
public boolean getAccelerateEnabled() { 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() { public boolean isNetworkEnergySufficient() {
return this.networkEnergySufficient; return this.networkEnergySufficient;
} }
public void setAccelerateEnabled(boolean accelerateEnabled) {
this.accelerateEnabled = accelerateEnabled;
}
/** /**
* 更新网络能量充足标记并在菜单存在且状态变化时触发同步 * 更新网络能量充足状态并通知菜单
*
* @param sufficient 是否能量充足 * @param sufficient 是否能量充足
*/ */
private void updateNetworkEnergySufficient(boolean sufficient) { private void updateNetworkEnergySufficient(boolean sufficient) {
// 保持部件内部状态一致部件为权威来源
this.networkEnergySufficient = sufficient; this.networkEnergySufficient = sufficient;
if (this.menu != null) { if (menu != null) {
try { menu.setNetworkEnergySufficient(sufficient);
// 使用菜单的封装方法更新并广播以保持封装性
this.menu.setNetworkEnergySufficient(sufficient);
} catch (Exception ignored) {}
} }
} }
/** /**
* 获取当前状态下的静态模型用于渲染 * 获取当前状态的渲染模型
*
* @return 当前状态的模型 * @return 当前状态的模型
*/ */
@Override
public IPartModel getStaticModels() { public IPartModel getStaticModels() {
if (this.isActive() && this.isPowered()) { if (this.isActive() && this.isPowered()) {
return MODELS_HAS_CHANNEL; return MODELS_HAS_CHANNEL;
@ -129,15 +172,15 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
} }
/** /**
* 当玩家激活部件右键时调用打开自定义菜单 * 处理玩家右键激活部件打开菜单
*
* @param player 玩家 * @param player 玩家
* @param hand * @param hand
* @param pos 点击位置 * @param pos 点击位置
* @return 总是返回 true表示激活成功 * @return 总是返回 true
*/ */
@Override @Override
public boolean onPartActivate(Player player, InteractionHand hand, Vec3 pos) { public boolean onPartActivate(Player player, InteractionHand hand, Vec3 pos) {
// 仅在服务端打开菜单
if (!player.getCommandSenderWorld().isClientSide()) { if (!player.getCommandSenderWorld().isClientSide()) {
MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this)); MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this));
} }
@ -145,7 +188,8 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
} }
/** /**
* 定义部件的碰撞箱用于物理碰撞和渲染 * 定义部件的碰撞箱
*
* @param bch 碰撞辅助器 * @param bch 碰撞辅助器
*/ */
@Override @Override
@ -155,160 +199,258 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
} }
/** /**
* 获取定时请求决定本部件多久 tick 一次 * 获取定时请求指定 tick 频率
*
* @param iGridNode 网络节点 * @param iGridNode 网络节点
* @return TickingRequest 对象 * @return TickingRequest 对象
*/ */
@Override @Override
public TickingRequest getTickingRequest(IGridNode iGridNode) { public TickingRequest getTickingRequest(IGridNode iGridNode) {
// 1 tick 执行一次
return new TickingRequest(1, 1, false, true); return new TickingRequest(1, 1, false, true);
} }
/** /**
* 当升级卡数量发生变化时调用通知菜单更新 * 当升级卡变化时通知菜单更新
*/ */
@Override @Override
public void upgradesChanged() { public void upgradesChanged() {
if (this.menu != null) { // 更新缓存的升级卡数量和加速倍率
// 使用 AE2 风格当升级发生变化时让菜单广播变化/数据会被同步客户端会基于槽内容重新计算并刷新界面 this.cachedEnergyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
this.menu.broadcastChanges(); this.cachedSpeed = calculateSpeed();
if (menu != null) {
menu.broadcastChanges();
} }
} }
/** /**
* 网络定时回调每次 tick 时调用 * 网络定时回调处理目标方块实体的加速
* @param iGridNode 网络节点 *
* @param ticksSinceLastCall 距离上次调用经过的 tick * @param iGridNode 网络节点
* @return TickRateModulation.IDLE 表示继续保持当前 tick 速率 * @param ticksSinceLastCall 距离上次调用的 tick
* @return TickRateModulation.IDLE
*/ */
@Override @Override
public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) { public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) {
// 如果部件的加速开关被关闭则不进行加速提前返回 if (!getAccelerateEnabled()) {
if (!this.getAccelerateEnabled()) {
return TickRateModulation.IDLE; return TickRateModulation.IDLE;
} }
updateCachedTarget();
// 获取目标方块实体本部件朝向的方块 if (cachedTarget != null && isActive()) {
BlockEntity target = getLevel().getBlockEntity(getBlockEntity().getBlockPos().relative(getSide())); ticker(cachedTarget);
// 仅在目标存在且部件处于激活状态时执行加速
if (target != null && isActive()) {
ticker(target);
} }
return TickRateModulation.IDLE; return TickRateModulation.IDLE;
} }
/** /**
* 以指定速度对目标方块实体进行 tick 操作 * 对目标方块实体执行加速 tick 操作
* @param blockEntity 需要被 tick 的方块实体 *
* @param <T> 方块实体类型 * @param blockEntity 目标方块实体
* @param <T> 方块实体类型
*/ */
private <T extends BlockEntity> void ticker(@NotNull T blockEntity) { private <T extends BlockEntity> void ticker(@NotNull T blockEntity) {
if (this.getGridNode() == null if (!isValidForTicking()) {
|| this.getMainNode() == null
|| this.getMainNode().getGrid() == null) {
return; return;
} }
// 获取方块实体的位置 String blockId = ForgeRegistries.BLOCKS.getKey(blockEntity.getBlockState().getBlock()).toString();
BlockPos pos = blockEntity.getBlockPos(); if (ConfigParsingUtils.isBlockBlacklisted(blockId)) {
if (blockEntity.getLevel() == null) return;
// 检查黑名单支持通配符/正则
String blockId = Objects.requireNonNull(ForgeRegistries.BLOCKS.getKey(blockEntity.getBlockState().getBlock())).toString();
// 使用工具类的缓存接口工具类内部负责懒加载/线程安全
List<Pattern> compiledBlacklist = ConfigParsingUtils.getCachedBlacklist(List.of(ModConfig.INSTANCE.entityTickerBlackList));
for (Pattern p : compiledBlacklist) {
if (p.matcher(blockId).matches()) return;
}
// 获取该方块实体的 Ticker
@SuppressWarnings("unchecked")
BlockEntityTicker<T> blockEntityTicker = this.getLevel()
.getBlockState(pos)
.getTicker(this.getLevel(), (BlockEntityType<T>) 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); // 能量不足
return; return;
} }
double extractedPower = getMainNode().getGrid().getEnergyService() BlockEntityTicker<T> ticker = getTicker(blockEntity);
.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG); if (ticker == null) {
if (extractedPower < requiredPower) {
updateNetworkEnergySufficient(false); // 能量不足
return; return;
} }
updateNetworkEnergySufficient(true); // 能量充足
// 计算加速倍数基于 2 的次方并把 8 张映射到最大 1024x2^10 if (cachedEnergyCardCount == -1 || cachedSpeed == -1) {
// 已由 product 计算得到 speed上面已在没有卡时提前返回 this.cachedEnergyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
this.cachedSpeed = calculateSpeed();
}
// 执行 tick 操作 if (cachedSpeed <= 0) {
for (int i = 0; i < speed - 1; i++) { return;
blockEntityTicker.tick( }
blockEntity.getLevel(),
blockEntity.getBlockPos(), double requiredPower = PowerUtils.getCachedPower(cachedSpeed, cachedEnergyCardCount)
blockEntity.getBlockState(), * ConfigParsingUtils.getMultiplierForBlock(blockId);
blockEntity 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);
} }
} }
/** /**
* 判断部件是否有自定义名称 * 获取目标方块实体的 ticker
* @return 是否有自定义名称 *
* @param blockEntity 目标方块实体
* @return ticker null
*/ */
private <T extends BlockEntity> BlockEntityTicker<T> getTicker(T blockEntity) {
return getLevel().getBlockState(blockEntity.getBlockPos())
.getTicker(getLevel(), (BlockEntityType<T>) 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 <T extends BlockEntity> void performTicks(T blockEntity,
BlockEntityTicker<T> 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 @Override
public boolean hasCustomName() { public boolean hasCustomName() {
return super.hasCustomName(); return super.hasCustomName();
} }
/**
* 获取部件的显示名称
* @return 显示名称
*/
@Override @Override
public @NotNull Component getDisplayName() { public @NotNull Component getDisplayName() {
return super.getDisplayName(); return super.getDisplayName();
} }
/** /**
* 创建自定义菜单GUI * 创建菜单实例
* @param containerId 容器ID *
* @param containerId 容器ID
* @param playerInventory 玩家背包 * @param playerInventory 玩家背包
* @param player 玩家 * @param player 玩家
* @return 菜单实例 * @return 菜单实例
*/ */
@Override @Override
@ -319,11 +461,19 @@ public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTicka
} }
/** /**
* 获取可用的升级卡槽数量 * 获取升级卡槽数量
*
* @return 升级卡槽数量 * @return 升级卡槽数量
*/ */
@Override @Override
protected int getUpgradeSlots() { protected int getUpgradeSlots() {
return 8; return 8;
} }
@Override
public void removeFromWorld() {
super.removeFromWorld();
cachedTarget = null;
cachedTargetPos = null;
}
} }

View File

@ -11,129 +11,115 @@ import appeng.util.Platform;
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu; import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.ToggleEntityTickerC2SPacket; 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.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/**
* 实体加速器界面显示加速状态卡数量能耗和倍率信息
*/
public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends UpgradeableScreen<C> { public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends UpgradeableScreen<C> {
private boolean eap$entitySpeedTickerEnabled = false; private boolean eap$entitySpeedTickerEnabled = false; // 本地缓存的加速开关状态
private SettingToggleButton<YesNo> eap$entitySpeedTickerToggle; private final SettingToggleButton<YesNo> 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); 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<YesNo> 的外观原版图标但自定义悬停描述为智能阻挡
// 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传
eap$entitySpeedTickerToggle = new SettingToggleButton<>( eap$entitySpeedTickerToggle = new SettingToggleButton<>(
Settings.BLOCKING_MODE, Settings.BLOCKING_MODE,
this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO, this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO,
(btn, backwards) -> { (btn, backwards) ->
// 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传 ModNetwork.CHANNEL.sendToServer(new ToggleEntityTickerC2SPacket())
ModNetwork.CHANNEL.sendToServer(new ToggleEntityTickerC2SPacket());
}
) { ) {
@Override @Override
public List<Component> getTooltipMessage() { public List<Component> getTooltipMessage() {
// 如果目标在黑名单中直接显示已禁用的提示 if (menu.targetBlacklisted) {
try { return List.of(
if (menu != null && menu.targetBlacklisted) { Component.literal("实体加速"),
var title = Component.literal("实体加速"); Component.literal("已禁用(目标在黑名单)")
var stateLine = Component.literal("已禁用(目标在黑名单)"); );
return List.of(title, stateLine); }
}
} catch (Exception ignored) {}
boolean enabled = eap$entitySpeedTickerEnabled; boolean enabled = eap$entitySpeedTickerEnabled;
var title = Component.literal("实体加速"); return List.of(
var stateLine = enabled Component.literal("实体加速"),
? Component.literal("已启用: 将加速目标方块实体的tick") enabled ? Component.literal("已启用: 将加速目标方块实体的tick") :
: Component.literal("已关闭: 不会对目标方块实体进行加速"); Component.literal("已关闭: 不会对目标方块实体进行加速")
return List.of(title, stateLine); );
} }
@Override @Override
protected Icon getIcon() { protected Icon getIcon() {
try { if (menu.targetBlacklisted) return Icon.INVALID;
if (menu != null && menu.targetBlacklisted) { return this.getCurrentValue() == YesNo.YES ? Icon.VALID : Icon.INVALID;
// 黑名单时显示禁用图标
return Icon.INVALID;
}
} catch (Exception ignored) {}
// 根据当前值显示不同图标可按需替换 Icon 常量
if (this.getCurrentValue() == YesNo.YES) {
return Icon.VALID;
} else {
return Icon.INVALID;
}
} }
}; };
// 初始化后立刻对齐当前@GuiSync状态避免首帧显示不一致
eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO); eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO);
this.addToLeftToolbar(eap$entitySpeedTickerToggle); this.addToLeftToolbar(eap$entitySpeedTickerToggle);
} }
/**
* 在渲染前更新界面状态
*/
@Override @Override
protected void updateBeforeRender() { protected void updateBeforeRender() {
super.updateBeforeRender(); super.updateBeforeRender();
if (eap$entitySpeedTickerToggle != null && menu != null) {
if (this.eap$entitySpeedTickerToggle != null) { eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
boolean desired = this.eap$entitySpeedTickerEnabled; // 如果目标在黑名单禁用按钮并显示关闭状态
if (this.menu != null) { eap$entitySpeedTickerToggle.set(menu.targetBlacklisted ? YesNo.NO : (eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO));
desired = this.menu.getAccelerateEnabled(); eap$entitySpeedTickerToggle.active = !menu.targetBlacklisted;
}
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;
}
} }
textData(); textData();
} }
/**
* 刷新界面显示
*/
public void refreshGui() { public void refreshGui() {
textData(); textData();
} }
/**
* 更新界面文本内容包括加速状态速度能耗和倍率
*/
private void textData() { private void textData() {
// 如果目标被黑名单禁止则显示禁用状态并把数值显示为 0 Map<String, Component> textContents = new HashMap<>();
if (getMenu().targetBlacklisted) { if (getMenu().targetBlacklisted || !getMenu().getAccelerateEnabled()) {
setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable")); // 黑名单禁用或加速关闭时的默认显示
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0)); textContents.put("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable"));
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false))); textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0));
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0))); textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false)));
setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0))); textContents.put("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0)));
return; 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; textContents.put("enable", getMenu().networkEnergySufficient ? null :
double multiplier = getMenu().multiplier; Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient"));
int effectiveSpeed = getMenu().effectiveSpeed; textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed));
double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount); textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false)));
double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount); 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)));
if (!getMenu().networkEnergySufficient) {
setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient"));
}else {
setTextContent("enable", null);
} }
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed)); textContents.forEach(this::setTextContent);
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)));
} }
} }

View File

@ -1,4 +1,4 @@
package com.extendedae_plus.wireless; package com.extendedae_plus.ae.wireless;
import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNode;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;

View File

@ -1,6 +1,9 @@
package com.extendedae_plus.wireless; package com.extendedae_plus.ae.wireless;
import net.minecraft.server.level.ServerLevel; 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 final IWirelessEndpoint host;
private long frequency; // 0 为未设置 private long frequency; // 0 为未设置
private boolean registered; private boolean registered;
@Nullable
private UUID placerId; // 放置者UUID
public WirelessMasterLink(IWirelessEndpoint host) { public WirelessMasterLink(IWirelessEndpoint host) {
this.host = host; this.host = host;
} }
public void setPlacerId(@Nullable UUID placerId) {
this.placerId = placerId;
}
public long getFrequency() { return frequency; } public long getFrequency() { return frequency; }
@ -43,7 +52,8 @@ public class WirelessMasterLink {
public boolean register() { public boolean register() {
ServerLevel level = host.getServerLevel(); ServerLevel level = host.getServerLevel();
if (level == null || frequency == 0L) return false; 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; this.registered = ok;
return ok; return ok;
} }
@ -51,7 +61,8 @@ public class WirelessMasterLink {
public void unregister() { public void unregister() {
ServerLevel level = host.getServerLevel(); ServerLevel level = host.getServerLevel();
if (!registered || level == null || frequency == 0L) return; if (!registered || level == null || frequency == 0L) return;
WirelessMasterRegistry.unregister(level, frequency, host); // placerId可以为null公共收发器模式
WirelessMasterRegistry.unregister(level, frequency, placerId, host);
registered = false; registered = false;
} }

View File

@ -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.config.ModConfig;
import com.extendedae_plus.util.WirelessTeamUtil;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
/** /**
* 无线主端注册中心 维度 + 频率 唯一注册一个主收发器端点 * 无线主端注册中心 维度 + 频率 + 所有者 唯一注册一个主收发器端点
* 从端通过本注册中心按频率查找主端实现一对多连接 * 从端通过本注册中心按频率查找主端实现一对多连接
* 所有者隔离有FTBTeams时同队共享没有时每个玩家独立
* 公共模式placerId为null时使用公共UUID所有人都能访问向下兼容旧版本
*/ */
public final class WirelessMasterRegistry { public final class WirelessMasterRegistry {
private WirelessMasterRegistry() {} private WirelessMasterRegistry() {}
private static final Map<Key, WeakReference<IWirelessEndpoint>> MASTERS = new HashMap<>(); private static final Map<Key, WeakReference<IWirelessEndpoint>> 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(level, "level");
Objects.requireNonNull(endpoint, "endpoint"); Objects.requireNonNull(endpoint, "endpoint");
if (frequency == 0L) return false; 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); cleanupIfCleared(key);
var existing = MASTERS.get(key); var existing = MASTERS.get(key);
var existingVal = existing == null ? null : existing.get(); var existingVal = existing == null ? null : existing.get();
if (existingVal != null && !existingVal.isEndpointRemoved()) { if (existingVal != null && !existingVal.isEndpointRemoved()) {
// 同维度同频率已经有主端 // 同维度同频率同所有者已经有主端
return false; return false;
} }
MASTERS.put(key, new WeakReference<>(endpoint)); MASTERS.put(key, new WeakReference<>(endpoint));
return true; 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; 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); var ref = MASTERS.get(key);
if (ref != null) { if (ref != null) {
var cur = ref.get(); 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; 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); cleanupIfCleared(key);
var ref = MASTERS.get(key); var ref = MASTERS.get(key);
return ref == null ? null : ref.get(); return ref == null ? null : ref.get();
@ -66,9 +97,12 @@ public final class WirelessMasterRegistry {
return ModConfig.INSTANCE.wirelessCrossDimEnable; return ModConfig.INSTANCE.wirelessCrossDimEnable;
} }
private record Key(ResourceKey<Level> dim, long freq) { private record Key(@Nullable ResourceKey<Level> dim, long freq, UUID owner) {
@Override public String toString() { @Override
return (dim == null ? "*" : dim.location().toString()) + "#" + freq; public String toString() {
return (dim == null ? "*" : dim.location().toString())
+ "#" + freq
+ "@" + owner;
} }
} }
} }

View File

@ -1,22 +1,26 @@
package com.extendedae_plus.wireless; package com.extendedae_plus.ae.wireless;
import appeng.api.networking.GridHelper; import appeng.api.networking.GridHelper;
import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNode;
import appeng.me.service.helpers.ConnectionWrapper; import appeng.me.service.helpers.ConnectionWrapper;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
/** /**
* 从收发器连接器 * 从收发器连接器
* - 通过频率查找同维度主收发器 * - 通过频率查找同维度主收发器
* - 校验距离<= ModConfigs.WIRELESS_MAX_RANGE * - 校验距离<= ModConfigs.WIRELESS_MAX_RANGE
* - 动态创建/销毁 AE2 连接GridConnection实现一主多从 * - 动态创建/销毁 AE2 连接GridConnection实现"一主多从"
*/ */
public class WirelessSlaveLink { public class WirelessSlaveLink {
private final IWirelessEndpoint host; private final IWirelessEndpoint host;
private long frequency; // 0 未设置 private long frequency; // 0 未设置
@Nullable
private UUID placerId; // 放置者UUID
private ConnectionWrapper connection = new ConnectionWrapper(null); private ConnectionWrapper connection = new ConnectionWrapper(null);
private boolean shutdown = true; private boolean shutdown = true;
@ -25,6 +29,10 @@ public class WirelessSlaveLink {
public WirelessSlaveLink(IWirelessEndpoint host) { public WirelessSlaveLink(IWirelessEndpoint host) {
this.host = Objects.requireNonNull(host); this.host = Objects.requireNonNull(host);
} }
public void setPlacerId(@Nullable UUID placerId) {
this.placerId = placerId;
}
public void setFrequency(long frequency) { public void setFrequency(long frequency) {
if (this.frequency != frequency) { if (this.frequency != frequency) {
@ -60,7 +68,8 @@ public class WirelessSlaveLink {
return; return;
} }
IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency); // placerId可以为null公共收发器模式
IWirelessEndpoint master = WirelessMasterRegistry.get(level, frequency, placerId);
shutdown = false; shutdown = false;
distance = 0.0D; distance = 0.0D;

View File

@ -1,7 +1,7 @@
package com.extendedae_plus.wireless.endpoint; package com.extendedae_plus.ae.wireless.endpoint;
import appeng.api.networking.IGridNode; 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.core.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;

View File

@ -1,8 +1,8 @@
package com.extendedae_plus.wireless.endpoint; package com.extendedae_plus.ae.wireless.endpoint;
import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNode;
import appeng.helpers.InterfaceLogicHost; 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.core.BlockPos;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;

View File

@ -1,6 +1,6 @@
package com.extendedae_plus.api; package com.extendedae_plus.api;
public interface AdvancedBlockingHolder { public interface IAdvancedBlocking {
boolean eap$getAdvancedBlocking(); boolean eap$getAdvancedBlocking();
void eap$setAdvancedBlocking(boolean value); void eap$setAdvancedBlocking(boolean value);

View File

@ -3,7 +3,7 @@ package com.extendedae_plus.api;
/** /**
* {@code GuiExPatternProviderMixin} 实现用于从通用的 Screen Mixin 中更新按钮布局 * {@code GuiExPatternProviderMixin} 实现用于从通用的 Screen Mixin 中更新按钮布局
*/ */
public interface ExPatternButtonsAccessor { public interface IExPatternButtonsAccessor {
/** /**
* 在每帧调用以维护扩展样板供应器右侧按钮的可见性重注册窗口尺寸变化与定位 * 在每帧调用以维护扩展样板供应器右侧按钮的可见性重注册窗口尺寸变化与定位
*/ */

View File

@ -3,6 +3,6 @@ package com.extendedae_plus.api;
/** /**
* GuiExPatternProviderMixin 实现用于在客户端侧提供当前页号避免反射读取 AE2 内部字段失败 * GuiExPatternProviderMixin 实现用于在客户端侧提供当前页号避免反射读取 AE2 内部字段失败
*/ */
public interface ExPatternPageAccessor { public interface IExPatternPageAccessor {
int eap$getCurrentPage(); int eap$getCurrentPage();
} }

View File

@ -1,5 +1,5 @@
package com.extendedae_plus.api; package com.extendedae_plus.api;
public interface PatternProviderMenuAdvancedSync { public interface IPatternProviderMenuAdvancedSync {
boolean eap$getAdvancedBlockingSynced(); boolean eap$getAdvancedBlockingSynced();
} }

View File

@ -1,5 +0,0 @@
package com.extendedae_plus.api;
public interface PatternProviderMenuDoublingSync {
boolean eap$getSmartDoublingSynced();
}

View File

@ -1,6 +0,0 @@
package com.extendedae_plus.api;
public interface SmartDoublingAwarePattern {
boolean eap$allowScaling();
void eap$setAllowScaling(boolean allow);
}

View File

@ -1,6 +0,0 @@
package com.extendedae_plus.api;
public interface SmartDoublingHolder {
boolean eap$getSmartDoubling();
void eap$setSmartDoubling(boolean value);
}

View File

@ -1,9 +1,9 @@
package com.extendedae_plus.bridge; package com.extendedae_plus.api.bridge;
/** /**
* mixin 包下的桥接接口 mixin 进行 instanceof 检测和回调 * mixin 包下的桥接接口 mixin 进行 instanceof 检测和回调
*/ */
public interface InterfaceWirelessLinkBridge { public interface IInterfaceWirelessLinkBridge {
void eap$updateWirelessLink(); void eap$updateWirelessLink();
/** /**

View File

@ -1,4 +1,4 @@
package com.extendedae_plus.bridge; package com.extendedae_plus.api.bridge;
import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeInventory;
import appeng.menu.ToolboxMenu; import appeng.menu.ToolboxMenu;

View File

@ -0,0 +1,7 @@
package com.extendedae_plus.api.smartDoubling;
public interface IPatternProviderMenuDoublingSync {
boolean eap$getSmartDoublingSynced();
int eap$getScalingLimit();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -8,8 +8,8 @@ import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.integration.jei.JeiRuntimeProxy; import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor; import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor;
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; 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.PullFromJeiOrCraftC2SPacket;
import com.extendedae_plus.network.crafting.OpenCraftFromJeiC2SPacket;
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
import mezz.jei.api.ingredients.ITypedIngredient; import mezz.jei.api.ingredients.ITypedIngredient;
import net.minecraft.client.Minecraft; 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.api.distmarker.Dist;
import net.minecraftforge.client.event.ScreenEvent; import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.Mod;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.util.Optional; import java.util.Optional;
@ -33,10 +33,7 @@ public final class InputEvents {
if (!ModList.get().isLoaded("jei")) { if (!ModList.get().isLoaded("jei")) {
return; return;
} }
// JEI 运行时尚未就绪跳过 // 注意不要在 try/catch 之外直接访问 JEI 运行时避免类加载崩溃
if (JeiRuntimeProxy.get() == null) {
return;
}
// 优先处理Shift + 左键拉取或下单 // 优先处理Shift + 左键拉取或下单
if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) { if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) {
try { try {
@ -103,9 +100,7 @@ public final class InputEvents {
if (!ModList.get().isLoaded("jei")) { if (!ModList.get().isLoaded("jei")) {
return; return;
} }
if (JeiRuntimeProxy.get() == null) { // 注意不要在 try/catch 之外直接访问 JEI 运行时避免类加载崩溃
return;
}
if (event.getKeyCode() != GLFW.GLFW_KEY_F) return; if (event.getKeyCode() != GLFW.GLFW_KEY_F) return;
// 仅当鼠标确实悬停在 JEI 配料上时触发 // 仅当鼠标确实悬停在 JEI 配料上时触发

View File

@ -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));
}
}

View File

@ -2,7 +2,7 @@ package com.extendedae_plus.client.screen;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.menu.NetworkPatternControllerMenu; 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.components.Button;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;

View File

@ -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();
}
}
}

View File

@ -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) { private String buildLabel(int idx) {
String name = fNames.get(idx); String name = fNames.get(idx);
int totalSlots = fTotalSlots.get(idx); int totalSlots = fTotalSlots.get(idx);
int count = fCount.get(idx); int count = fCount.get(idx);
// 不显示具体 id显示合并统计名称总空位x数量 // 不显示具体 id显示合并统计名称总空位x数量
return name + " (" + totalSlots + ") x" + count; return name + " (" + totalSlots + ") x" + count;
} }
@ -215,7 +234,11 @@ public class ProviderSelectScreen extends Screen {
String name = names.get(i); String name = names.get(i);
long id = ids.get(i); long id = ids.get(i);
int slots = emptySlots.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.count++;
g.totalSlots += Math.max(0, slots); g.totalSlots += Math.max(0, slots);
// 挑选空位最多的作为代表 id若并列保留先到者 // 挑选空位最多的作为代表 id若并列保留先到者

View File

@ -1,6 +1,6 @@
package com.extendedae_plus.compat; package com.extendedae_plus.compat;
import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.util.Logger;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
/** /**
@ -27,7 +27,7 @@ public class CompatibilityTest {
if (appfluxExists) { if (appfluxExists) {
} else { } else {
} }
} }
/** /**

View File

@ -2,7 +2,6 @@ package com.extendedae_plus.compat;
import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeInventory;
import appeng.api.upgrades.IUpgradeableObject; import appeng.api.upgrades.IUpgradeableObject;
import appeng.api.upgrades.UpgradeInventories;
import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.ToolboxPanel; import appeng.client.gui.widgets.ToolboxPanel;
import appeng.client.gui.widgets.UpgradesPanel; import appeng.client.gui.widgets.UpgradesPanel;
@ -11,7 +10,7 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.SlotSemantics; import appeng.menu.SlotSemantics;
import appeng.menu.ToolboxMenu; 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.network.chat.Component;
import net.minecraft.world.inventory.Slot; import net.minecraft.world.inventory.Slot;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
@ -41,12 +40,7 @@ public class UpgradeSlotCompat {
*/ */
public static boolean shouldEnableUpgradeSlots() { public static boolean shouldEnableUpgradeSlots() {
boolean appfluxExists = isAppfluxPresent(); boolean appfluxExists = isAppfluxPresent();
return !appfluxExists;
if (appfluxExists) {
return false;
} else {
return true;
}
} }
/** /**
@ -89,7 +83,7 @@ public class UpgradeSlotCompat {
setupUpgradesMethod.setAccessible(true); setupUpgradesMethod.setAccessible(true);
setupUpgradesMethod.invoke(menu, upgradeableHost.getUpgrades()); setupUpgradesMethod.invoke(menu, upgradeableHost.getUpgrades());
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("反射调用setupUpgrades失败", e); Logger.EAP$LOGGER.error("反射调用setupUpgrades失败", e);
return false; return false;
} }
@ -97,12 +91,10 @@ public class UpgradeSlotCompat {
if (menu instanceof IUpgradeableMenuCompat compatMenu) { if (menu instanceof IUpgradeableMenuCompat compatMenu) {
compatMenu.setCompatToolbox(toolbox); compatMenu.setCompatToolbox(toolbox);
} }
ExtendedAELogger.LOGGER.debug("成功为PatternProviderMenu初始化升级功能");
return true; return true;
} }
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("初始化PatternProviderMenu升级功能时出错", e); Logger.EAP$LOGGER.error("初始化PatternProviderMenu升级功能时出错", e);
} }
return false; return false;
@ -144,8 +136,6 @@ public class UpgradeSlotCompat {
ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName());
addMethod.invoke(widgets, "toolbox", toolboxPanel); addMethod.invoke(widgets, "toolbox", toolboxPanel);
} }
ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板");
return true; return true;
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// 尝试其他可能的方法签名 // 尝试其他可能的方法签名
@ -170,17 +160,15 @@ public class UpgradeSlotCompat {
ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName());
putMethod.invoke(widgets, "toolbox", toolboxPanel); putMethod.invoke(widgets, "toolbox", toolboxPanel);
} }
ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板使用put方法");
return true; return true;
} catch (Exception e2) { } catch (Exception e2) {
ExtendedAELogger.LOGGER.error("反射调用widgets方法失败", e2); Logger.EAP$LOGGER.error("反射调用widgets方法失败", e2);
return false; return false;
} }
} }
} }
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("为PatternProviderScreen添加升级面板时出错", e); Logger.EAP$LOGGER.error("为PatternProviderScreen添加升级面板时出错", e);
} }
return false; return false;
@ -199,7 +187,7 @@ public class UpgradeSlotCompat {
list.addAll(appeng.api.upgrades.Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem())); list.addAll(appeng.api.upgrades.Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem()));
} }
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("获取兼容升级列表时出错", e); Logger.EAP$LOGGER.error("获取兼容升级列表时出错", e);
} }
return list; return list;

View File

@ -1,18 +1,30 @@
package com.extendedae_plus.config; package com.extendedae_plus.config;
import com.extendedae_plus.ExtendedAEPlus; 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.Configuration;
import dev.toma.configuration.client.IValidationHandler;
import dev.toma.configuration.config.Config; import dev.toma.configuration.config.Config;
import dev.toma.configuration.config.Configurable; import dev.toma.configuration.config.Configurable;
import dev.toma.configuration.config.format.ConfigFormats; 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) @Config(id = ExtendedAEPlus.MODID)
public final class ModConfig { public final class ModConfig {
public static ModConfig INSTANCE; public static ModConfig INSTANCE;
private static final Object lock = new Object(); 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() { public static void init() {
synchronized (lock) { synchronized (lock) {
@ -21,6 +33,7 @@ public final class ModConfig {
} }
} }
} }
@Configurable @Configurable
@Configurable.Comment(value = { @Configurable.Comment(value = {
"扩展样板供应器总槽位容量的倍率。", "扩展样板供应器总槽位容量的倍率。",
@ -95,6 +108,7 @@ public final class ModConfig {
}) })
@Configurable.Range(min = 0, max = Integer.MAX_VALUE) @Configurable.Range(min = 0, max = Integer.MAX_VALUE)
@Configurable.Synchronized @Configurable.Synchronized
@Configurable.ValueUpdateCallback(method = "onEntityTickerCostUpdate")
public int entityTickerCost = 512; public int entityTickerCost = 512;
@Configurable @Configurable
@ -103,9 +117,8 @@ public final class ModConfig {
"格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'" "格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
}) })
@Configurable.Synchronized @Configurable.Synchronized
public String[] entityTickerBlackList = { @Configurable.ValueUpdateCallback(method = "onEntityTickerBlackListUpdate")
public String[] entityTickerBlackList = {};
};
@Configurable @Configurable
@Configurable.Comment(value = { @Configurable.Comment(value = {
@ -113,9 +126,8 @@ public final class ModConfig {
"支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。" "支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
}) })
@Configurable.Synchronized @Configurable.Synchronized
public String[] entityTickerMultipliers = { @Configurable.ValueUpdateCallback(method = "onEntityTickerMultipliersUpdate")
public String[] entityTickerMultipliers = {};
};
@Configurable @Configurable
@Configurable.Comment(value = { @Configurable.Comment(value = {
@ -133,4 +145,51 @@ public final class ModConfig {
@Configurable.Synchronized @Configurable.Synchronized
public boolean restrictCraftingPatternToMolecular = false; 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);
}
}
} }

View File

@ -1,11 +1,15 @@
package com.extendedae_plus.content.wireless; package com.extendedae_plus.content.wireless;
import com.extendedae_plus.ae.items.ChannelCardItem;
import com.extendedae_plus.init.ModBlockEntities; import com.extendedae_plus.init.ModBlockEntities;
import com.extendedae_plus.init.ModItems;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block; 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.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
public class WirelessTransceiverBlock extends Block implements EntityBlock { public class WirelessTransceiverBlock extends Block implements EntityBlock {
public WirelessTransceiverBlock(Properties props) { public WirelessTransceiverBlock(Properties props) {
@ -27,28 +34,71 @@ public class WirelessTransceiverBlock extends Block implements EntityBlock {
} }
@Override @Override
public void attack(BlockState state, Level level, BlockPos pos, Player player) { public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
// 潜行左键减频-1 -10 super.setPlacedBy(level, pos, state, placer, stack);
if (!level.isClientSide && player.isShiftKeyDown()) { if (!level.isClientSide && placer instanceof Player player) {
BlockEntity be = level.getBlockEntity(pos); BlockEntity be = level.getBlockEntity(pos);
if (be instanceof WirelessTransceiverBlockEntity te) { if (be instanceof WirelessTransceiverBlockEntity te) {
if (te.isLocked()) { te.setPlacerId(player.getUUID(), player.getName().getString());
player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true); }
}
}
@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); super.attack(state, level, pos, player);
return; return;
} }
int step = 1;
if (player.getMainHandItem().is(Items.REDSTONE_TORCH)) step = 10; // 潜行左键其他物品减频-1 -10
if (player.getMainHandItem().is(Items.STICK)) step = 10; if (player.isShiftKeyDown()) {
long f = te.getFrequency(); if (te.isLocked()) {
f -= step; player.displayClientMessage(Component.literal("收发器已锁定,无法修改频道"), true);
if (f < 0) f = 0; super.attack(state, level, pos, player);
te.setFrequency(f); return;
player.displayClientMessage(Component.literal("频道:" + te.getFrequency()), true); }
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); 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 @Override
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {

View File

@ -3,11 +3,11 @@ package com.extendedae_plus.content.wireless;
import appeng.api.networking.*; import appeng.api.networking.*;
import appeng.api.util.AECableType; import appeng.api.util.AECableType;
import appeng.blockentity.AEBaseBlockEntity; 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.ModBlockEntities;
import com.extendedae_plus.init.ModItems; 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.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
/** /**
* 无线收发器方块实体骨架 * 无线收发器方块实体骨架
@ -25,6 +26,7 @@ import java.util.Objects;
* - 频率设置 * - 频率设置
* - 集成 AE2 节点 * - 集成 AE2 节点
* - 集成无线主/从逻辑 * - 集成无线主/从逻辑
* - 支持FTBTeams队伍隔离软依赖
*/ */
public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost { public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
@ -33,6 +35,11 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements
private long frequency = 1L; private long frequency = 1L;
private boolean masterMode = false; private boolean masterMode = false;
private boolean locked = false; private boolean locked = false;
@Nullable
private UUID placerId; // 放置者UUID用于队伍隔离
@Nullable
private String placerName; // 放置者名称用于显示
private WirelessMasterLink masterLink; private WirelessMasterLink masterLink;
private WirelessSlaveLink slaveLink; 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() { public long getFrequency() {
return frequency; return frequency;
} }
@ -192,6 +236,12 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements
tag.putLong("frequency", frequency); tag.putLong("frequency", frequency);
tag.putBoolean("master", masterMode); tag.putBoolean("master", masterMode);
tag.putBoolean("locked", locked); tag.putBoolean("locked", locked);
if (placerId != null) {
tag.putUUID("placerId", placerId);
}
if (placerName != null) {
tag.putString("placerName", placerName);
}
if (managedNode != null) { if (managedNode != null) {
managedNode.saveToNBT(tag); managedNode.saveToNBT(tag);
} }
@ -203,6 +253,16 @@ public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements
this.frequency = tag.getLong("frequency"); this.frequency = tag.getLong("frequency");
this.masterMode = tag.getBoolean("master"); this.masterMode = tag.getBoolean("master");
this.locked = tag.getBoolean("locked"); 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) { if (managedNode != null) {
managedNode.loadFromNBT(tag); managedNode.loadFromNBT(tag);

View File

@ -2,9 +2,11 @@ package com.extendedae_plus.hooks;
import appeng.util.InteractionUtil; import appeng.util.InteractionUtil;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.client.ui.FrequencyInputScreen;
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
import appeng.block.crafting.CraftingUnitBlock; import appeng.block.crafting.CraftingUnitBlock;
import appeng.blockentity.crafting.CraftingBlockEntity; import appeng.blockentity.crafting.CraftingBlockEntity;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; 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.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; 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.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.SubscribeEvent; 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));
}
} }

View File

@ -2,6 +2,11 @@ package com.extendedae_plus.init;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.network.*; 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.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.NetworkRegistry;
@ -67,6 +72,12 @@ public class ModNetwork {
.consumerNetworkThread(SetPatternHighlightS2CPacket::handle) .consumerNetworkThread(SetPatternHighlightS2CPacket::handle)
.add(); .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) CHANNEL.messageBuilder(SetProviderPageS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
.encoder(SetProviderPageS2CPacket::encode) .encoder(SetProviderPageS2CPacket::encode)
.decoder(SetProviderPageS2CPacket::decode) .decoder(SetProviderPageS2CPacket::decode)
@ -85,6 +96,12 @@ public class ModNetwork {
.consumerNetworkThread(ToggleSmartDoublingC2SPacket::handle) .consumerNetworkThread(ToggleSmartDoublingC2SPacket::handle)
.add(); .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) CHANNEL.messageBuilder(GlobalToggleProviderModesC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
.encoder(GlobalToggleProviderModesC2SPacket::encode) .encoder(GlobalToggleProviderModesC2SPacket::encode)
.decoder(GlobalToggleProviderModesC2SPacket::decode) .decoder(GlobalToggleProviderModesC2SPacket::decode)
@ -120,6 +137,18 @@ public class ModNetwork {
.decoder(CraftingMonitorOpenProviderC2SPacket::decode) .decoder(CraftingMonitorOpenProviderC2SPacket::decode)
.consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle) .consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle)
.add(); .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++; } private static int nextId() { return id++; }

View File

@ -6,14 +6,8 @@ import appeng.core.definitions.AEItems;
import appeng.core.definitions.AEParts; import appeng.core.definitions.AEParts;
import appeng.core.localization.GuiText; import appeng.core.localization.GuiText;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 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.*;
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;
/** /**
* *

View File

@ -66,6 +66,39 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr
tooltip.add(Component.literal((usable ? "设备在线" : "设备离线"))); 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; private final ResourceLocation uid;

View File

@ -2,11 +2,13 @@ package com.extendedae_plus.integration.jade;
import appeng.api.networking.IGrid; import appeng.api.networking.IGrid;
import appeng.api.networking.IGridNode; 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.content.wireless.WirelessTransceiverBlockEntity;
import com.extendedae_plus.wireless.IWirelessEndpoint; import com.extendedae_plus.util.WirelessTeamUtil;
import com.extendedae_plus.wireless.WirelessMasterRegistry;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import snownee.jade.api.BlockAccessor; import snownee.jade.api.BlockAccessor;
import snownee.jade.api.IServerDataProvider; import snownee.jade.api.IServerDataProvider;
@ -40,11 +42,46 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
} }
} }
data.putBoolean("networkUsable", networkUsable); data.putBoolean("networkUsable", networkUsable);
// 添加频道使用信息参考AE2的 IUsedChannelProvider 实现
int usedChannels = 0;
int maxChannels = 0;
if (node != null && node.isActive()) {
// 遍历该节点的所有连接取使用频道数的最大值
for (var connection : node.getConnections()) {
usedChannels = Math.max(connection.getUsedChannels(), usedChannels);
}
// 获取节点的最大频道容量致密线缆为32
if (node instanceof appeng.me.GridNode gridNode) {
var channelMode = gridNode.getGrid().getPathingService().getChannelMode();
if (channelMode == appeng.api.networking.pathing.ChannelMode.INFINITE) {
maxChannels = -1; // 无限频道
} else {
maxChannels = gridNode.getMaxChannels();
}
}
}
data.putInt("usedChannels", usedChannels);
data.putInt("maxChannels", maxChannels);
// 添加所有者信息有FTBTeams时显示团队否则显示玩家
var placerId = blockEntity.getPlacerId();
if (placerId != null) {
data.putUUID("placerId", placerId);
var level = blockEntity.getServerLevel();
if (level != null) {
// 使用WirelessTeamUtil自动判断显示团队或玩家名称
Component ownerName = WirelessTeamUtil.getNetworkOwnerName(level, placerId);
data.putString("ownerName", ownerName.getString());
}
}
// 如果是从模式查询主节点位置与维度 // 如果是从模式查询主节点位置与维度
if (!blockEntity.isMasterMode()) { if (!blockEntity.isMasterMode()) {
var level = blockEntity.getServerLevel(); var level = blockEntity.getServerLevel();
long freq = blockEntity.getFrequency(); long freq = blockEntity.getFrequency();
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq); // 复用上面的placerId变量
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq, placerId);
if (master != null && !master.isEndpointRemoved()) { if (master != null && !master.isEndpointRemoved()) {
if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) { if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) {
data.putString("customName", masterBlockEntity.getCustomName().getString()); data.putString("customName", masterBlockEntity.getCustomName().getString());

View File

@ -0,0 +1,181 @@
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.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;
/**
* 将所有会引用 JEI GUI 内部类 BookmarkListIngredientBookmarkIElement 的逻辑
* 隔离在此桥接类中避免在未安装 JEI JEI 组件不完整时过早类加载导致的 NoClassDefFoundError
*
* 该类仅会被 {@link JeiRuntimeProxy} 通过反射调用
*/
@Pseudo
public final class JeiBookmarkBridge {
private JeiBookmarkBridge() {}
// 通过 JeiRuntimeProxy 的包内可见方法安全地获取 Runtime
private static @Nullable IJeiRuntime getRuntime() {
try {
Class<?> 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<? extends ITypedIngredient<?>> 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<ITypedIngredient<ItemStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(VanillaTypes.ITEM_STACK, stack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<ItemStack> 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<ITypedIngredient<FluidStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<FluidStack> bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
list.add(bookmark); // add 内部会自动保存到配置
});
}
}
/**
* 如果存在 Mekanism/appmek则将 Mekanism 化学堆栈添加到 JEI 书签
*/
public static void addBookmark(Object chemicalStack) {
if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return;
IJeiRuntime rt = 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<ITypedIngredient<ItemStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(VanillaTypes.ITEM_STACK, stack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<ItemStack> bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
list.remove(bookmark);
});
}
}
}

View File

@ -1,24 +1,16 @@
package com.extendedae_plus.integration.jei; package com.extendedae_plus.integration.jei;
import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor;
import mezz.jei.api.constants.VanillaTypes; 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.ingredients.ITypedIngredient;
import mezz.jei.api.runtime.IBookmarkOverlay; import mezz.jei.api.runtime.IBookmarkOverlay;
import mezz.jei.api.runtime.IIngredientListOverlay; import mezz.jei.api.runtime.IIngredientListOverlay;
import mezz.jei.api.runtime.IJeiRuntime; 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.minecraft.world.item.ItemStack;
import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Pseudo;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -131,48 +123,35 @@ public final class JeiRuntimeProxy {
} }
/** /**
* 获取JEI书签列表 * 获取 JEI 书签列表为避免在未安装 JEI GUI 时崩溃使用反射委托到桥接类
*/ */
public static List<? extends ITypedIngredient<?>> getBookmarkList() { public static List<? extends ITypedIngredient<?>> getBookmarkList() {
IJeiRuntime rt = RUNTIME; try {
if (rt == null) return Collections.emptyList(); Class<?> bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge");
IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay(); var m = bridge.getMethod("getBookmarkList");
if (bookmarkOverlay instanceof BookmarkOverlayAccessor accessor) { @SuppressWarnings("unchecked")
BookmarkList bookmarkList = accessor.eap$getBookmarkList(); List<? extends ITypedIngredient<?>> list = (List<? extends ITypedIngredient<?>>) m.invoke(null);
return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList(); return list == null ? Collections.emptyList() : list;
} catch (Throwable ignored) {
return Collections.emptyList();
} }
return Collections.emptyList();
} }
public static void addBookmark(ItemStack stack) { public static void addBookmark(ItemStack stack) {
IJeiRuntime rt = RUNTIME; try {
if (rt == null) return; Class<?> bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge");
var m = bridge.getMethod("addBookmark", ItemStack.class);
IBookmarkOverlay overlay = rt.getBookmarkOverlay(); m.invoke(null, stack);
if (overlay instanceof BookmarkOverlayAccessor accessor) { } catch (Throwable ignored) {
BookmarkList list = accessor.eap$getBookmarkList();
Optional<ITypedIngredient<ItemStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(VanillaTypes.ITEM_STACK, stack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<ItemStack> bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
list.add(bookmark); // add 内部会自动保存到配置
});
} }
} }
public static void addBookmark(FluidStack fluidStack) { public static void addBookmark(FluidStack fluidStack) {
IJeiRuntime rt = RUNTIME; try {
if (rt == null) return; Class<?> bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge");
var m = bridge.getMethod("addBookmark", FluidStack.class);
IBookmarkOverlay overlay = rt.getBookmarkOverlay(); m.invoke(null, fluidStack);
if (overlay instanceof BookmarkOverlayAccessor accessor) { } catch (Throwable ignored) {
BookmarkList list = accessor.eap$getBookmarkList();
Optional<ITypedIngredient<FluidStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(ForgeTypes.FLUID_STACK, fluidStack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<FluidStack> bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
list.add(bookmark); // add 内部会自动保存到配置
});
} }
} }
@ -180,91 +159,25 @@ public final class JeiRuntimeProxy {
* 如果存在 Mekanism/appmek则将 Mekanism 化学堆栈添加到 JEI 书签 * 如果存在 Mekanism/appmek则将 Mekanism 化学堆栈添加到 JEI 书签
*/ */
public static void addBookmark(Object chemicalStack) { public static void addBookmark(Object chemicalStack) {
if (!ModList.get().isLoaded("mekanism") && !ModList.get().isLoaded("appmek")) return; try {
Class<?> bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge");
IJeiRuntime rt = RUNTIME; var m = bridge.getMethod("addBookmark", Object.class);
if (rt == null) return; m.invoke(null, chemicalStack);
} catch (Throwable ignored) {
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 { // Note: helper methods moved to bridge to avoid referencing JEI GUI at class load time.
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 书签移除物品 * JEI 书签移除物品反射委托
*/ */
public static void removeBookmark(ItemStack stack) { public static void removeBookmark(ItemStack stack) {
IJeiRuntime rt = RUNTIME; try {
if (rt == null) return; Class<?> bridge = Class.forName("com.extendedae_plus.integration.jei.JeiBookmarkBridge");
var m = bridge.getMethod("removeBookmark", ItemStack.class);
IBookmarkOverlay overlay = rt.getBookmarkOverlay(); m.invoke(null, stack);
if (overlay instanceof BookmarkOverlayAccessor accessor) { } catch (Throwable ignored) {
BookmarkList list = accessor.eap$getBookmarkList();
Optional<ITypedIngredient<ItemStack>> typedOpt = rt.getIngredientManager()
.createTypedIngredient(VanillaTypes.ITEM_STACK, stack);
typedOpt.ifPresent(typed -> {
IngredientBookmark<ItemStack> bookmark = IngredientBookmark.create(typed, rt.getIngredientManager());
list.remove(bookmark);
});
} }
} }
} }

View File

@ -1,6 +1,5 @@
package com.extendedae_plus.mixin; package com.extendedae_plus.mixin;
import net.minecraftforge.fml.ModList;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@ -27,7 +26,6 @@ public class MixinConditions implements IMixinConfigPlugin {
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
// 对于升级相关的Mixin检查appflux是否存在 // 对于升级相关的Mixin检查appflux是否存在
if (mixinClassName.contains("PatternProviderMenuUpgradesMixin") || if (mixinClassName.contains("PatternProviderMenuUpgradesMixin") ||
mixinClassName.contains("PatternProviderScreenUpgradesMixin") ||
mixinClassName.contains("PatternProviderLogicUpgradesMixin") || mixinClassName.contains("PatternProviderLogicUpgradesMixin") ||
mixinClassName.contains("PatternProviderLogicHostUpgradesMixin")) { mixinClassName.contains("PatternProviderLogicHostUpgradesMixin")) {

View File

@ -1,7 +1,7 @@
package com.extendedae_plus.mixin.advancedae; package com.extendedae_plus.mixin.advancedae;
import appeng.api.crafting.IPatternDetails; 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 net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;

View File

@ -1,17 +1,19 @@
package com.extendedae_plus.mixin.advancedae.client.gui; package com.extendedae_plus.mixin.advancedae.client.gui;
import appeng.api.config.Settings;
import appeng.api.config.YesNo; import appeng.api.config.YesNo;
import appeng.client.gui.AEBaseScreen; import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.SettingToggleButton; import appeng.client.gui.widgets.SettingToggleButton;
import com.extendedae_plus.api.ExPatternButtonsAccessor; import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync;
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync;
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket; import com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket;
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket; import com.extendedae_plus.network.provider.ToggleAdvancedBlockingC2SPacket;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; 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.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.pedroksl.advanced_ae.client.gui.AdvPatternProviderScreen; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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样板供应器界面添加高级阻挡模式按钮 * 为高级ae样板供应器界面添加高级阻挡模式按钮
@ -31,116 +36,160 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
*/ */
@Mixin(AdvPatternProviderScreen.class) @Mixin(AdvPatternProviderScreen.class)
public abstract class AdvPatternProviderScreenMixin extends AEBaseScreen<AdvPatternProviderMenu> { public abstract class AdvPatternProviderScreenMixin extends AEBaseScreen<AdvPatternProviderMenu> {
// 高级阻挡模式切换按钮
@Unique private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
// 智能翻倍切换按钮
@Unique private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
// 智能翻倍上限输入框
@Unique private EditBox eap$PerProviderLimitInput;
@Unique // 当前高级阻挡模式是否启用
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle; @Unique private boolean eap$AdvancedBlockingEnabled = false;
// 当前智能翻倍是否启用
@Unique @Unique private boolean eap$SmartDoublingEnabled = false;
private boolean eap$AdvancedBlockingEnabled = false; // 当前智能翻倍上限
@Unique private int eap$PerProviderScalingLimit = 0;
@Unique
private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
@Unique
private boolean eap$SmartDoublingEnabled = false;
public AdvPatternProviderScreenMixin(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { public AdvPatternProviderScreenMixin(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
super(menu, playerInventory, title, style); super(menu, playerInventory, title, style);
} }
@Inject(method = "<init>", at = @At("RETURN")) /** 同步服务端状态(初始化时调用) */
private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { @Unique
// 使用 @GuiSync 初始化 private void eap$syncInitialState(AdvPatternProviderMenu menu) {
try { try {
if (menu instanceof PatternProviderMenuAdvancedSync sync) { // 同步高级阻挡和智能翻倍的服务端状态
if (menu instanceof IPatternProviderMenuAdvancedSync sync) {
this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
} }
} catch (Throwable t) { if (menu instanceof IPatternProviderMenuDoublingSync sync) {
LOGGER.error("Error initializing advanced sync", t); this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced();
} this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit();
// 使用 SettingToggleButton<YesNo> 的外观原版图标但自定义悬停描述为智能阻挡
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<Component> getTooltipMessage() {
boolean enabled = eap$AdvancedBlockingEnabled;
var title = Component.literal("智能阻挡");
var line = enabled
? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)")
: Component.literal("已禁用:这么好的功能为什么不打开呢");
return java.util.List.of(title, line);
}
};
// 初始化后立刻对齐当前@GuiSync状态避免首帧显示不一致
this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
// 智能翻倍按钮与高级阻挡同款样式点击仅发送C2S状态由@GuiSync驱动
try {
if (menu instanceof PatternProviderMenuDoublingSync sync2) {
this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced();
} }
} catch (Throwable t) { } 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<Component> 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 = "<init>", 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) @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
private void eap$updateAdvancedBlocking(CallbackInfo ci) { private void eap$updateBeforeRender(CallbackInfo ci) {
if (this.eap$AdvancedBlockingToggle != null) { // 每帧刷新按钮和输入框状态
boolean desired = this.eap$AdvancedBlockingEnabled; eap$updateToggles();
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) { eap$updateLimitInput();
desired = sync.eap$getAdvancedBlockingSynced(); }
}
this.eap$AdvancedBlockingEnabled = desired; /* ---------------------------- 刷新逻辑 ---------------------------- */
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO); /**
* 刷新切换按钮的状态与服务端同步
*/
@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 focused = this.eap$PerProviderLimitInput.isFocused();
boolean desired2 = this.eap$SmartDoublingEnabled; // 如果未聚焦且服务端有变化则同步显示
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) { if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) {
desired2 = sync2.eap$getSmartDoublingSynced(); this.eap$PerProviderScalingLimit = remoteLimit;
} this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit));
this.eap$SmartDoublingEnabled = desired2;
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
} }
if ((Object) this instanceof GuiExPatternProvider) { if (this.eap$SmartDoublingEnabled) {
try { // 智能翻倍启用时确保输入框可见
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); if (!this.renderables.contains(this.eap$PerProviderLimitInput)) {
} catch (Throwable t) { this.addRenderableWidget(this.eap$PerProviderLimitInput);
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
} }
// 未聚焦且内容为空时显示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);
} }
} }
} }

View File

@ -1,20 +1,23 @@
package com.extendedae_plus.mixin.advancedae.client.gui; package com.extendedae_plus.mixin.advancedae.client.gui;
import appeng.api.config.Settings;
import appeng.api.config.YesNo; import appeng.api.config.YesNo;
import appeng.client.gui.AEBaseScreen; import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.SettingToggleButton; import appeng.client.gui.widgets.SettingToggleButton;
import com.extendedae_plus.api.ExPatternButtonsAccessor; import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync;
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync;
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket; import com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket;
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket; import com.extendedae_plus.network.provider.ToggleAdvancedBlockingC2SPacket;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; 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.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.pedroksl.advanced_ae.client.gui.SmallAdvPatternProviderScreen; 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 net.pedroksl.advanced_ae.gui.advpatternprovider.SmallAdvPatternProviderMenu;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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样板供应器界面添加高级阻挡模式按钮 * 为高级ae样板供应器界面添加高级阻挡模式按钮
@ -31,116 +37,160 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
*/ */
@Mixin(SmallAdvPatternProviderScreen.class) @Mixin(SmallAdvPatternProviderScreen.class)
public abstract class SmallAdvPatternProviderScreenMixin extends AEBaseScreen<SmallAdvPatternProviderMenu> { public abstract class SmallAdvPatternProviderScreenMixin extends AEBaseScreen<SmallAdvPatternProviderMenu> {
// 高级阻挡模式切换按钮
@Unique private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
// 智能翻倍切换按钮
@Unique private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
// 智能翻倍上限输入框
@Unique private EditBox eap$PerProviderLimitInput;
@Unique // 当前高级阻挡模式是否启用
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle; @Unique private boolean eap$AdvancedBlockingEnabled = false;
// 当前智能翻倍是否启用
@Unique @Unique private boolean eap$SmartDoublingEnabled = false;
private boolean eap$AdvancedBlockingEnabled = false; // 当前智能翻倍上限
@Unique private int eap$PerProviderScalingLimit = 0;
@Unique
private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
@Unique
private boolean eap$SmartDoublingEnabled = false;
public SmallAdvPatternProviderScreenMixin(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) { public SmallAdvPatternProviderScreenMixin(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
super(menu, playerInventory, title, style); super(menu, playerInventory, title, style);
} }
@Inject(method = "<init>", at = @At("RETURN")) /** 同步服务端状态(初始化时调用) */
private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { @Unique
// 使用 @GuiSync 初始化 private void eap$syncInitialState(AdvPatternProviderMenu menu) {
try { try {
if (menu instanceof PatternProviderMenuAdvancedSync sync) { // 同步高级阻挡和智能翻倍的服务端状态
if (menu instanceof IPatternProviderMenuAdvancedSync sync) {
this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
} }
} catch (Throwable t) { if (menu instanceof IPatternProviderMenuDoublingSync sync) {
LOGGER.error("Error initializing advanced sync", t); this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced();
} this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit();
// 使用 SettingToggleButton<YesNo> 的外观原版图标但自定义悬停描述为智能阻挡
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<Component> getTooltipMessage() {
boolean enabled = eap$AdvancedBlockingEnabled;
var title = Component.literal("智能阻挡");
var line = enabled
? Component.literal("已启用:对于同一种配方将不再阻挡(需要开启原版的阻挡模式)")
: Component.literal("已禁用:这么好的功能为什么不打开呢");
return java.util.List.of(title, line);
}
};
// 初始化后立刻对齐当前@GuiSync状态避免首帧显示不一致
this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
// 智能翻倍按钮与高级阻挡同款样式点击仅发送C2S状态由@GuiSync驱动
try {
if (menu instanceof PatternProviderMenuDoublingSync sync2) {
this.eap$SmartDoublingEnabled = sync2.eap$getSmartDoublingSynced();
} }
} catch (Throwable t) { } 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<Component> 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 = "<init>", 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) @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
private void eap$updateAdvancedBlocking(CallbackInfo ci) { private void eap$updateBeforeRender(CallbackInfo ci) {
if (this.eap$AdvancedBlockingToggle != null) { // 每帧刷新按钮和输入框状态
boolean desired = this.eap$AdvancedBlockingEnabled; eap$updateToggles();
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) { eap$updateLimitInput();
desired = sync.eap$getAdvancedBlockingSynced(); }
}
this.eap$AdvancedBlockingEnabled = desired; /* ---------------------------- 刷新逻辑 ---------------------------- */
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO); /**
* 刷新切换按钮的状态与服务端同步
*/
@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 focused = this.eap$PerProviderLimitInput.isFocused();
boolean desired2 = this.eap$SmartDoublingEnabled; // 如果未聚焦且服务端有变化则同步显示
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) { if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) {
desired2 = sync2.eap$getSmartDoublingSynced(); this.eap$PerProviderScalingLimit = remoteLimit;
} this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit));
this.eap$SmartDoublingEnabled = desired2;
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
} }
if ((Object) this instanceof GuiExPatternProvider) { if (this.eap$SmartDoublingEnabled) {
try { // 智能翻倍启用时确保输入框可见
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); if (!this.renderables.contains(this.eap$PerProviderLimitInput)) {
} catch (Throwable t) { this.addRenderableWidget(this.eap$PerProviderLimitInput);
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
} }
// 未聚焦且内容为空时显示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);
} }
} }
} }

View File

@ -5,7 +5,7 @@ import appeng.api.crafting.IPatternDetails.IInput;
import appeng.api.stacks.AEKey; import appeng.api.stacks.AEKey;
import appeng.api.stacks.GenericStack; import appeng.api.stacks.GenericStack;
import appeng.helpers.patternprovider.PatternProviderTarget; 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.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections; import java.util.Collections;
@Mixin(value = AdvPatternProviderLogic.class, remap = false) @Mixin(value = AdvPatternProviderLogic.class, remap = false)
public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder { public class AdvPatternProviderLogicAdvancedMixin implements IAdvancedBlocking {
@Unique @Unique
private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking"; private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking";

View File

@ -2,8 +2,8 @@ package com.extendedae_plus.mixin.advancedae.helpers;
import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails;
import appeng.crafting.pattern.AEProcessingPattern; import appeng.crafting.pattern.AEProcessingPattern;
import com.extendedae_plus.api.SmartDoublingAwarePattern; import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern;
import com.extendedae_plus.api.SmartDoublingHolder; import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder;
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = AdvPatternProviderLogic.class, remap = false) import static com.extendedae_plus.util.smartDoubling.PatternScaler.getComputedMul;
public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Unique
private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling";
@Unique @Mixin(value = AdvPatternProviderLogic.class, remap = false)
private boolean eap$smartDoubling = 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 @Override
public boolean eap$getSmartDoubling() { public boolean eap$getSmartDoubling() {
@ -31,23 +33,30 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder
@Override @Override
public void eap$setSmartDoubling(boolean value) { public void eap$setSmartDoubling(boolean value) {
this.eap$smartDoubling = value; this.eap$smartDoubling = value;
// 立即将开关状态应用到当前 Provider 的样板上避免等待下一次 updatePatterns
try { 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) // 触发一次刷新让网络及时拿到最新状态也会触发 ICraftingProvider.requestUpdate(mainNode)
((AdvPatternProviderLogic) (Object) this).updatePatterns(); ((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")) @Inject(method = "writeToNBT", at = @At("TAIL"))
private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) { private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) {
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
// 保存供应器级别上限
tag.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit);
} }
@Inject(method = "readFromNBT", at = @At("TAIL")) @Inject(method = "readFromNBT", at = @At("TAIL"))
@ -55,6 +64,9 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder
if (tag.contains(EAP_SMART_DOUBLING_KEY)) { if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
this.eap$smartDoubling = tag.getBoolean(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")) @Inject(method = "updatePatterns", at = @At("TAIL"))
@ -62,13 +74,14 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder
try { try {
var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
boolean allow = this.eap$smartDoubling; boolean allow = this.eap$smartDoubling;
int limit = this.eap$providerScalingLimit;
for (IPatternDetails details : list) { for (IPatternDetails details : list) {
if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) { if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern pattern) {
aware.eap$setAllowScaling(allow); pattern.eap$setAllowScaling(allow);
pattern.eap$setMultiplierLimit(getComputedMul(proc, limit));
} }
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
@Shadow @Shadow
@ -77,6 +90,7 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
private void onExportSettings(CompoundTag output, CallbackInfo ci) { private void onExportSettings(CompoundTag output, CallbackInfo ci) {
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); 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")) @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 // 持久化到 world
this.saveChanges(); this.saveChanges();
} }
if (input.contains(EAP_PROVIDER_SCALING_LIMIT)) {
this.eap$providerScalingLimit = input.getInt(EAP_PROVIDER_SCALING_LIMIT);
this.saveChanges();
}
} }
} }

View File

@ -2,8 +2,8 @@ package com.extendedae_plus.mixin.advancedae.menu;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.guisync.GuiSync; import appeng.menu.guisync.GuiSync;
import com.extendedae_plus.api.AdvancedBlockingHolder; import com.extendedae_plus.api.IAdvancedBlocking;
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AdvPatternProviderMenu.class) @Mixin(AdvPatternProviderMenu.class)
public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync { public abstract class AdvPatternProviderMenuAdvancedMixin implements IPatternProviderMenuAdvancedSync {
@Final @Final
@Shadow(remap = false) @Shadow(remap = false)
protected AdvPatternProviderLogic logic; protected AdvPatternProviderLogic logic;
// 选择一个未占用的 GUI 同步 idAE2 已用到 7这里使用 21 以避冲突 // 选择一个未占用的 GUI 同步 idAE2 已用到 7这里使用 21 以避冲突
@Unique @Unique
@GuiSync(22) @GuiSync(20)
public boolean eap$AdvancedBlocking = false; public boolean eap$AdvancedBlocking = false;
@Inject(method = "broadcastChanges", at = @At("HEAD")) @Inject(method = "broadcastChanges", at = @At("HEAD"))
@ -33,7 +33,7 @@ public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProv
// 避免@Shadow父类方法改用公共APIAEBaseMenu#isClientSide() // 避免@Shadow父类方法改用公共APIAEBaseMenu#isClientSide()
if (!((AEBaseMenu) (Object) this).isClientSide()) { if (!((AEBaseMenu) (Object) this).isClientSide()) {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); 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) { private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
} }
} catch (Throwable ignored) {} } 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) { private void eap$initAdvancedSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
} }
} catch (Throwable ignored) {} } catch (Throwable ignored) {}

View File

@ -2,8 +2,8 @@ package com.extendedae_plus.mixin.advancedae.menu;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.guisync.GuiSync; import appeng.menu.guisync.GuiSync;
import com.extendedae_plus.api.PatternProviderMenuDoublingSync; import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync;
import com.extendedae_plus.api.SmartDoublingHolder; import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(AdvPatternProviderMenu.class) @Mixin(AdvPatternProviderMenu.class)
public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync { public abstract class AdvPatternProviderMenuDoublingMixin implements IPatternProviderMenuDoublingSync {
@Final @Final
@Shadow(remap = false) @Shadow(remap = false)
protected AdvPatternProviderLogic logic; protected AdvPatternProviderLogic logic;
@Unique @Unique
@GuiSync(23) @GuiSync(21)
public boolean eap$SmartDoubling = false; public boolean eap$SmartDoubling = false;
@Unique
@GuiSync(22)
public int eap$PerProviderScalingLimit = 0; // 0 = no limit
@Inject(method = "broadcastChanges", at = @At("HEAD")) @Inject(method = "broadcastChanges", at = @At("HEAD"))
private void eap$syncSmartDoubling(CallbackInfo ci) { private void eap$syncSmartDoubling(CallbackInfo ci) {
if (!((AEBaseMenu) (Object) this).isClientSide()) { if (!((AEBaseMenu) (Object) this).isClientSide()) {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); 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) { private void eap$initSmartSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); this.eap$SmartDoubling = holder.eap$getSmartDoubling();
this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit();
} }
} catch (Throwable ignored) {} } 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) { private void eap$initSmartSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); this.eap$SmartDoubling = holder.eap$getSmartDoubling();
this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit();
} }
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
} }
@ -61,4 +67,9 @@ public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProv
public boolean eap$getSmartDoublingSynced() { public boolean eap$getSmartDoublingSynced() {
return this.eap$SmartDoubling; return this.eap$SmartDoubling;
} }
@Override
public int eap$getScalingLimit() {
return this.eap$PerProviderScalingLimit;
}
} }

View File

@ -1,14 +1,16 @@
package com.extendedae_plus.mixin.ae2; package com.extendedae_plus.mixin.ae2;
import appeng.crafting.pattern.AEProcessingPattern; 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.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@Mixin(value = AEProcessingPattern.class, remap = false) @Mixin(value = AEProcessingPattern.class, remap = false)
public class AEProcessingPatternMixin implements SmartDoublingAwarePattern { public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern {
@Unique @Unique
private boolean eap$allowScaling = false; // 默认不允许缩放 private boolean eap$allowScaling = false; // 默认不允许缩放
@Unique
private int eap$multiplierLimit = 0; // 模式级别的倍数上限0 表示不限制
@Override @Override
public boolean eap$allowScaling() { public boolean eap$allowScaling() {
@ -19,4 +21,14 @@ public class AEProcessingPatternMixin implements SmartDoublingAwarePattern {
public void eap$setAllowScaling(boolean allow) { public void eap$setAllowScaling(boolean allow) {
this.eap$allowScaling = 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);
}
} }

View File

@ -1,36 +1,74 @@
package com.extendedae_plus.mixin.ae2; package com.extendedae_plus.mixin.ae2;
import appeng.blockentity.crafting.CraftingBlockEntity; import appeng.blockentity.crafting.CraftingBlockEntity;
import appeng.blockentity.crafting.CraftingMonitorBlockEntity;
import appeng.me.cluster.implementations.CraftingCPUCluster; 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.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
@Mixin(value = CraftingCPUCluster.class, remap = false, priority = 2000) @Mixin(value = CraftingCPUCluster.class, remap = false, priority = 2000)
public abstract class CraftingCPUClusterMixin { public abstract class CraftingCPUClusterMixin {
// 1) 提升单方块线程上限的常量避免抛出 IAE IllegalArgumentException
@ModifyConstant( @Shadow(remap = false)
private MachineSource machineSrc;
@Shadow(remap = false)
@Final
private List<CraftingBlockEntity> blockEntities;
@Shadow(remap = false)
@Final
private List<CraftingMonitorBlockEntity> status;
@Shadow(remap = false)
private long storage;
@Shadow(remap = false)
private int accelerator;
/**
* 完全替换 addBlockEntity 方法移除 16 线程限制
* 在方法开始时注入检查线程数如果超过 16 就手动处理并取消原方法
*/
@Inject(
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", 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) { private void extendedae_plus$removeThreadLimit(CraftingBlockEntity te, CallbackInfo ci) {
// 放宽到极大值完全取消单方块 16 线程的硬限制 // 只有当线程数超过 16 时才接管处理
return Integer.MAX_VALUE; if (te.getAcceleratorThreads() > 16) {
} // 手动实现 addBlockEntity 的逻辑但不检查 16 的限制
if (this.machineSrc == null || te.isCoreBlock()) {
// 2) 保持统计使用原始线程值若存在多处调用不再返回固定 16 this.machineSrc = new MachineSource(te);
@Redirect( }
method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V",
at = @At( te.setCoreBlock(false);
value = "INVOKE", te.saveChanges();
target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I", this.blockEntities.add(0, te);
ordinal = 1
) if (te instanceof CraftingMonitorBlockEntity) {
) this.status.add((CraftingMonitorBlockEntity) te);
private int extendedae_plus$onGetThreadsForLimitCheck(CraftingBlockEntity te) { }
// 返回原始线程数确保总并行单元不被错误下限 if (te.getStorageBytes() > 0) {
return te.getAcceleratorThreads(); this.storage += te.getStorageBytes();
}
if (te.getAcceleratorThreads() > 0) {
// 这里不检查 <= 16 的限制直接添加
this.accelerator += te.getAcceleratorThreads();
}
// 取消原方法执行
ci.cancel();
}
// 如果 <= 16让原方法正常执行
} }
} }

View File

@ -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<T extends AEBaseMenu> {
// 空接口避免在 AEBaseScreen 上声明不存在方法的 Invoker 导致编译错误
}

View File

@ -1,11 +1,14 @@
package com.extendedae_plus.mixin.ae2.accessor; package com.extendedae_plus.mixin.ae2.accessor;
import appeng.api.crafting.IPatternDetails;
import appeng.api.networking.IManagedGridNode; import appeng.api.networking.IManagedGridNode;
import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.helpers.patternprovider.PatternProviderLogicHost;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(PatternProviderLogic.class) @Mixin(PatternProviderLogic.class)
public interface PatternProviderLogicAccessor { public interface PatternProviderLogicAccessor {
@Accessor(value = "host", remap = false) @Accessor(value = "host", remap = false)
@ -13,4 +16,7 @@ public interface PatternProviderLogicAccessor {
@Accessor(value = "mainNode", remap = false) @Accessor(value = "mainNode", remap = false)
IManagedGridNode eap$mainNode(); IManagedGridNode eap$mainNode();
@Accessor(value = "patterns" , remap = false)
List<IPatternDetails> eap$patterns();
} }

View File

@ -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<AEKey> eap$patternInputs();
}

View File

@ -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<IPatternDetails> eap$patterns();
}

View File

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

View File

@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.ae2.autopattern;
import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails;
import appeng.me.service.CraftingService; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;

View File

@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.autopattern;
import appeng.api.stacks.KeyCounter; import appeng.api.stacks.KeyCounter;
import appeng.crafting.CraftingTreeNode; import appeng.crafting.CraftingTreeNode;
import appeng.crafting.inv.CraftingSimulationState; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;

View File

@ -9,11 +9,11 @@ import appeng.crafting.CraftingTreeNode;
import appeng.crafting.CraftingTreeProcess; import appeng.crafting.CraftingTreeProcess;
import appeng.crafting.pattern.AEProcessingPattern; import appeng.crafting.pattern.AEProcessingPattern;
import appeng.me.service.CraftingService; 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.config.ModConfig;
import com.extendedae_plus.content.ScaledProcessingPattern; import com.extendedae_plus.util.smartDoubling.PatternScaler;
import com.extendedae_plus.util.PatternScaler; import com.extendedae_plus.util.smartDoubling.RequestedAmountHolder;
import com.extendedae_plus.util.RequestedAmountHolder;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable; 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.List;
import java.util.stream.StreamSupport; 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 * 注入 CraftingTreeProcess 构造器尾部 AEProcessingPattern 替换为 ScaledProcessingPattern
@ -36,20 +36,24 @@ public abstract class CraftingTreeProcessMixin {
argsOnly = true, argsOnly = true,
remap = false 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 { try {
// 若传入的 details 已经是缩放样板且原始样板不允许缩放则直接解包为原始样板 // 若传入的 details 已经是缩放样板且原始样板不允许缩放则直接解包为原始样板
if (details instanceof ScaledProcessingPattern sp) { if (details instanceof ScaledProcessingPattern sp) {
var proc0 = sp.getOriginal(); var originalPattern = sp.getOriginal();
if (proc0 instanceof SmartDoublingAwarePattern aware0 && !aware0.eap$allowScaling()) { if (originalPattern instanceof ISmartDoublingAwarePattern scalingAwarePattern && !scalingAwarePattern.eap$allowScaling()) {
return proc0; 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; return original;
} }
@ -89,10 +93,10 @@ public abstract class CraftingTreeProcessMixin {
} }
// 使用每-provider 的分配量来缩放样板 // 使用每-provider 的分配量来缩放样板
var scaled = PatternScaler.scale(proc, parentTarget, perProvider); var scaled = PatternScaler.scale(processingPattern, parentTarget, perProvider);
return scaled != null ? scaled : original; return scaled != null ? scaled : original;
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("构建倍增样板出错", e); EAP$LOGGER.warn("构建倍增样板出错", e);
e.printStackTrace(); e.printStackTrace();
return original; return original;
} }

View File

@ -2,7 +2,7 @@ package com.extendedae_plus.mixin.ae2.autopattern;
import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails;
import appeng.helpers.patternprovider.PatternProviderLogic; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.Redirect;

View File

@ -13,14 +13,14 @@ import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.style.Text; import appeng.client.gui.style.Text;
import appeng.client.gui.style.TextAlignment; import appeng.client.gui.style.TextAlignment;
import appeng.menu.slot.AppEngSlot; 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.content.ClientPatternHighlightStore;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket; import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor;
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket; import com.extendedae_plus.network.crafting.CraftingMonitorJumpC2SPacket;
import com.extendedae_plus.network.crafting.CraftingMonitorOpenProviderC2SPacket;
import com.extendedae_plus.util.GuiUtil; import com.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import com.mojang.logging.LogUtils;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font; import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
@ -40,17 +40,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(AEBaseScreen.class) @Mixin(AEBaseScreen.class)
public abstract class AEBaseScreenMixin { 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+左键 * AEBaseScreen mouseClicked 入口拦截 CraftingCPUScreen Shift+左键
* 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket * 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket
@ -77,8 +66,7 @@ public abstract class AEBaseScreenMixin {
} }
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key)); ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key));
cir.setReturnValue(true); cir.setReturnValue(true);
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
/** /**
@ -107,8 +95,7 @@ public abstract class AEBaseScreenMixin {
} }
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key)); ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
cir.setReturnValue(true); cir.setReturnValue(true);
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
@Unique @Unique
@ -266,7 +253,7 @@ public abstract class AEBaseScreenMixin {
int cur = 1; int cur = 1;
int max = 1; int max = 1;
if (self instanceof ExPatternPageAccessor accessor) { if (self instanceof IExPatternPageAccessor accessor) {
cur = Math.max(0, accessor.eap$getCurrentPage()) + 1; cur = Math.max(0, accessor.eap$getCurrentPage()) + 1;
} }
try { try {
@ -281,7 +268,7 @@ public abstract class AEBaseScreenMixin {
String pageText = "" + cur + "" + "/" + max + ""; String pageText = "" + cur + "" + "/" + max + "";
ScreenStyle style = eap$getStyle(self); ScreenStyle style = ((AEBaseScreenAccessor<?>) this).eap$getStyle();
int color = 0xFFFFFFFF; int color = 0xFFFFFFFF;
if (style != null) { if (style != null) {
try { try {
@ -299,11 +286,9 @@ public abstract class AEBaseScreenMixin {
guiGraphics.drawString(font, pageText, lineWidth + padding, 0, color, false); guiGraphics.drawString(font, pageText, lineWidth + padding, 0, color, false);
guiGraphics.pose().popPose(); guiGraphics.pose().popPose();
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
@Shadow(remap = false) @Shadow(remap = false)
protected void setTextContent(String id, Component content) {}; protected void setTextContent(String id, Component content) {};

View File

@ -3,17 +3,15 @@ package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.client.gui.AEBaseScreen; import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.implementations.InterfaceScreen; import appeng.client.gui.implementations.InterfaceScreen;
import appeng.menu.AEBaseMenu; 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 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.AbstractContainerScreenAccessor;
import com.extendedae_plus.mixin.accessor.ScreenAccessor; import com.extendedae_plus.mixin.accessor.ScreenAccessor;
import com.extendedae_plus.network.meInterface.InterfaceAdjustConfigAmountC2SPacket;
import com.glodblock.github.extendedae.client.button.ActionEPPButton; import com.glodblock.github.extendedae.client.button.ActionEPPButton;
import net.minecraft.client.gui.components.Tooltip; import net.minecraft.world.inventory.Slot;
import net.minecraft.network.chat.Component;
import com.mojang.logging.LogUtils;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -170,7 +168,7 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
// 获取菜单与配置槽列表 // 获取菜单与配置槽列表
var screen = (AEBaseScreen<?>) (Object) this; var screen = (AEBaseScreen<?>) (Object) this;
var menu = screen.getMenu(); var menu = screen.getMenu();
if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { if (!(menu instanceof InterfaceMenu interfaceMenu)) {
return; return;
} }
var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG);
@ -273,7 +271,7 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
} }
var screen = (AEBaseScreen<?>) (Object) this; var screen = (AEBaseScreen<?>) (Object) this;
var menu = screen.getMenu(); var menu = screen.getMenu();
if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) { if (!(menu instanceof InterfaceMenu interfaceMenu)) {
return; return;
} }
var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG); var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG);

View File

@ -11,6 +11,7 @@ import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor; import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor;
import com.extendedae_plus.mixin.accessor.ScreenAccessor; import com.extendedae_plus.mixin.accessor.ScreenAccessor;
import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor; 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.GuiGraphics;
import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.renderer.Rect2i; import net.minecraft.client.renderer.Rect2i;
@ -41,7 +42,7 @@ public abstract class PatternEncodingTermScreenMixin<T extends AEBaseMenu> {
// 复用已存在的按钮实例避免重复创建 // 复用已存在的按钮实例避免重复创建
if (eap$uploadBtn == null) { if (eap$uploadBtn == null) {
eap$uploadBtn = new IconButton(btn -> ModNetwork.CHANNEL 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 private final float eap$scale = 0.75f; // 12x12
@Override @Override

View File

@ -16,7 +16,6 @@ public class PatternProviderCloseMixin {
if (((Object) this) instanceof PatternProviderScreen) { if (((Object) this) instanceof PatternProviderScreen) {
ClientPatternHighlightStore.clearAll(); ClientPatternHighlightStore.clearAll();
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
} }

View File

@ -1,19 +1,23 @@
package com.extendedae_plus.mixin.ae2.client.gui; package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.api.config.Settings;
import appeng.api.config.YesNo; import appeng.api.config.YesNo;
import appeng.client.gui.AEBaseScreen; import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.implementations.PatternProviderScreen; import appeng.client.gui.implementations.PatternProviderScreen;
import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.SettingToggleButton; import appeng.client.gui.widgets.SettingToggleButton;
import appeng.menu.implementations.PatternProviderMenu; import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.api.ExPatternButtonsAccessor; import com.extendedae_plus.api.IExPatternButtonsAccessor;
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync;
import com.extendedae_plus.api.PatternProviderMenuDoublingSync; import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket; import com.extendedae_plus.network.provider.SetPerProviderScalingLimitC2SPacket;
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket; 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 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.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Mixin; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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 原版样板供应器界面添加高级阻挡模式按钮 * AE2 原版样板供应器界面添加高级阻挡模式按钮
@ -32,114 +39,174 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
@Mixin(PatternProviderScreen.class) @Mixin(PatternProviderScreen.class)
public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu> extends AEBaseScreen<C> { public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu> extends AEBaseScreen<C> {
@Unique // 高级阻挡模式切换按钮
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle; @Unique private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
// 智能翻倍切换按钮
@Unique private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
// 智能翻倍上限输入框
@Unique private EditBox eap$PerProviderLimitInput;
@Unique // 当前高级阻挡模式是否启用
private boolean eap$AdvancedBlockingEnabled = false; @Unique private boolean eap$AdvancedBlockingEnabled = false;
// 当前智能翻倍是否启用
@Unique @Unique private boolean eap$SmartDoublingEnabled = false;
private SettingToggleButton<YesNo> eap$SmartDoublingToggle; // 当前智能翻倍上限
@Unique private int eap$PerProviderScalingLimit = 0;
@Unique
private boolean eap$SmartDoublingEnabled = false;
public PatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { public PatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
super(menu, playerInventory, title, style); super(menu, playerInventory, title, style);
} }
@Inject(method = "<init>", at = @At("RETURN")) /** 同步服务端状态(初始化时调用) */
private void eap$initAdvancedBlocking(C menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { @Unique
// 使用 @GuiSync 初始化 private void eap$syncInitialState(C menu) {
try { try {
if (menu instanceof PatternProviderMenuAdvancedSync sync) { // 同步高级阻挡和智能翻倍的服务端状态
if (menu instanceof IPatternProviderMenuAdvancedSync sync) {
this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced(); this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
} }
} catch (Throwable t) { if (menu instanceof IPatternProviderMenuDoublingSync sync) {
LOGGER.error("Error initializing advanced sync", t); this.eap$SmartDoublingEnabled = sync.eap$getSmartDoublingSynced();
} this.eap$PerProviderScalingLimit = sync.eap$getScalingLimit();
// 使用 SettingToggleButton<YesNo> 的外观原版图标但自定义悬停描述为智能阻挡
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<net.minecraft.network.chat.Component> 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();
} }
} catch (Throwable t) { } 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<net.minecraft.network.chat.Component> 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 = "<init>", 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) @Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
private void eap$updateAdvancedBlocking(CallbackInfo ci) { private void eap$updateBeforeRender(CallbackInfo ci) {
if (this.eap$AdvancedBlockingToggle != null) { // 每帧刷新按钮和输入框状态
boolean desired = this.eap$AdvancedBlockingEnabled; eap$updateToggles();
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) { eap$updateLimitInput();
desired = sync.eap$getAdvancedBlockingSynced(); eap$updateExGuiLayout();
} }
this.eap$AdvancedBlockingEnabled = desired;
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO); /* ---------------------------- 刷新逻辑 ---------------------------- */
/**
* 刷新切换按钮的状态与服务端同步
*/
@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 focused = this.eap$PerProviderLimitInput.isFocused();
boolean desired2 = this.eap$SmartDoublingEnabled; // 如果未聚焦且服务端有变化则同步显示
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) { if (!focused && remoteLimit != this.eap$PerProviderScalingLimit) {
desired2 = sync2.eap$getSmartDoublingSynced(); this.eap$PerProviderScalingLimit = remoteLimit;
} this.eap$PerProviderLimitInput.setValue(String.valueOf(remoteLimit));
this.eap$SmartDoublingEnabled = desired2;
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
} }
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) { if ((Object) this instanceof GuiExPatternProvider) {
try { try {
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout(); ((IExPatternButtonsAccessor) this).eap$updateButtonsLayout();
} catch (Throwable t) { } catch (Throwable t) {
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString()); EAP$LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
} }
} }
} }

View File

@ -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<C extends PatternProviderMenu> extends AEBaseScreen<C> {
@Inject(method = "<init>", 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<Component> eap$getCompatibleUpgrades() {
var list = new ArrayList<Component>();
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);
}
}

View File

@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.client.Point; import appeng.client.Point;
import appeng.client.gui.layout.SlotGridLayout; import appeng.client.gui.layout.SlotGridLayout;
import com.extendedae_plus.api.IExPatternPageAccessor;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@ -32,7 +33,7 @@ public abstract class SlotGridLayoutMixin {
// 读取实际当前页码优先从 GUI accessor其次反射容器失败则为 0 // 读取实际当前页码优先从 GUI accessor其次反射容器失败则为 0
int currentPage = 0; int currentPage = 0;
try { try {
if (screen instanceof com.extendedae_plus.api.ExPatternPageAccessor accessor) { if (screen instanceof IExPatternPageAccessor accessor) {
currentPage = accessor.eap$getCurrentPage(); currentPage = accessor.eap$getCurrentPage();
} else { } else {
var menu = ((com.glodblock.github.extendedae.client.gui.GuiExPatternProvider) screen).getMenu(); var menu = ((com.glodblock.github.extendedae.client.gui.GuiExPatternProvider) screen).getMenu();

View File

@ -6,6 +6,7 @@ import appeng.menu.AEBaseMenu;
import appeng.menu.ToolboxMenu; import appeng.menu.ToolboxMenu;
import appeng.menu.implementations.PatternProviderMenu; import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.compat.UpgradeSlotCompat; import com.extendedae_plus.compat.UpgradeSlotCompat;
import com.extendedae_plus.util.Logger;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -43,7 +44,7 @@ public abstract class PatternProviderCompatMixin extends AEBaseMenu implements U
} }
} catch (Exception e) { } catch (Exception e) {
// 静默处理异常确保不会因为升级功能导致崩溃 // 静默处理异常确保不会因为升级功能导致崩溃
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e); Logger.EAP$LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e);
} }
} }

View File

@ -8,11 +8,12 @@ import appeng.api.upgrades.UpgradeInventories;
import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.helpers.patternprovider.PatternProviderLogicHost;
import com.extendedae_plus.ae.items.ChannelCardItem; 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.compat.UpgradeSlotCompat;
import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.util.Logger;
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
@ -31,7 +32,7 @@ import java.util.List;
* 根据appflux是否存在来决定是否实现IUpgradeableObject接口 * 根据appflux是否存在来决定是否实现IUpgradeableObject接口
*/ */
@Mixin(value = PatternProviderLogic.class, priority = 500, remap = false) @Mixin(value = PatternProviderLogic.class, priority = 500, remap = false)
public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, IInterfaceWirelessLinkBridge {
@Unique @Unique
private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty(); private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty();
@ -75,7 +76,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
eap$compatInitializeChannelLink(); eap$compatInitializeChannelLink();
} }
} catch (Exception e) { } 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(); eap$compatInitializeChannelLink();
} }
} catch (Exception e) { } 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")) at = @At("TAIL"))
private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) {
try { try {
boolean upgradeSlots = UpgradeSlotCompat.shouldEnableUpgradeSlots(); boolean upgradeSlots = UpgradeSlotCompat.shouldEnableUpgradeSlots();
boolean channelCard = UpgradeSlotCompat.shouldEnableChannelCard(); boolean channelCard = UpgradeSlotCompat.shouldEnableChannelCard();
if (upgradeSlots) { if (upgradeSlots) {
// 只有在升级槽功能启用时才创建升级槽 // 只有在升级槽功能启用时才创建升级槽
this.eap$compatUpgrades = UpgradeInventories.forMachine( this.eap$compatUpgrades = UpgradeInventories.forMachine(
@ -115,7 +116,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
} else { } else {
} }
} catch (Exception e) { } 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"); this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades");
} }
} catch (Exception e) { } 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) { } 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) { } 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(); this.eap$compatUpgrades.clear();
} }
} catch (Exception e) { } 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 { } else {
// 装了appflux时这个方法不应该被调用因为appflux的Mixin会覆盖它 // 装了appflux时这个方法不应该被调用因为appflux的Mixin会覆盖它
// 但是为了安全起见返回空的升级槽 // 但是为了安全起见返回空的升级槽
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("装了appflux时getUpgrades被调用这不应该发生");
return UpgradeInventories.empty(); return UpgradeInventories.empty();
} }
} }
@ -197,7 +197,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
eap$compatLink.updateStatus(); eap$compatLink.updateStatus();
} }
} catch (Exception e) { } 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) { if (this instanceof IUpgradeableObject) {
IUpgradeableObject upgradeableThis = (IUpgradeableObject) this; IUpgradeableObject upgradeableThis = (IUpgradeableObject) this;
upgrades = upgradeableThis.getUpgrades(); upgrades = upgradeableThis.getUpgrades();
com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("从appflux获取到升级槽: {}", upgrades != null);
} }
} catch (Exception e) { } 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) { for (ItemStack stack : upgrades) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack); channel = ChannelCardItem.getChannel(stack);
java.util.UUID ownerUUID = ChannelCardItem.getOwnerUUID(stack);
if (ownerUUID != null) {
// 保存ownerUUID到局部变量后面设置到link
channel |= ((long) ownerUUID.hashCode() << 32); // 临时存储
}
found = true; found = true;
break; break;
} }
@ -275,6 +279,18 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
eap$compatLink = new WirelessSlaveLink(endpoint); 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.setFrequency(channel);
eap$compatLink.updateStatus(); eap$compatLink.updateStatus();
@ -290,7 +306,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
} }
} catch (Exception e) { } 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(); return eap$compatLink != null && eap$compatLink.isConnected();
} }
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("检查兼容性无线连接状态失败", e); Logger.EAP$LOGGER.error("检查兼容性无线连接状态失败", e);
return false; return false;
} }
} }
@ -363,7 +379,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj
} }
} }
} catch (Exception e) { } 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 (Throwable ignored) {}
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性主节点状态变更处理失败", e); Logger.EAP$LOGGER.error("兼容性主节点状态变更处理失败", e);
} }
} }
} }

View File

@ -10,6 +10,7 @@ import appeng.core.localization.GuiText;
import appeng.menu.SlotSemantics; import appeng.menu.SlotSemantics;
import appeng.menu.implementations.PatternProviderMenu; import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.compat.UpgradeSlotCompat; import com.extendedae_plus.compat.UpgradeSlotCompat;
import com.extendedae_plus.util.Logger;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -38,7 +39,7 @@ public abstract class PatternProviderScreenCompatMixin<C extends PatternProvider
} }
} catch (Exception e) { } catch (Exception e) {
// 静默处理异常确保不会因为升级功能导致崩溃 // 静默处理异常确保不会因为升级功能导致崩溃
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderScreen兼容性升级面板初始化失败", e); Logger.EAP$LOGGER.error("PatternProviderScreen兼容性升级面板初始化失败", e);
} }
} }
@ -58,7 +59,7 @@ public abstract class PatternProviderScreenCompatMixin<C extends PatternProvider
} }
} }
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("直接添加升级面板失败", e); Logger.EAP$LOGGER.error("直接添加升级面板失败", e);
} }
} }
@ -75,7 +76,7 @@ public abstract class PatternProviderScreenCompatMixin<C extends PatternProvider
} }
} }
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取兼容升级列表失败", e); Logger.EAP$LOGGER.error("获取兼容升级列表失败", e);
} }
return list; return list;

View File

@ -4,11 +4,10 @@ import appeng.api.upgrades.IUpgradeInventory;
import appeng.helpers.InterfaceLogic; import appeng.helpers.InterfaceLogic;
import appeng.helpers.InterfaceLogicHost; import appeng.helpers.InterfaceLogicHost;
import com.extendedae_plus.ae.items.ChannelCardItem; 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.InterfaceNodeEndpointImpl;
import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge;
import com.extendedae_plus.init.ModItems; import com.extendedae_plus.init.ModItems;
import com.extendedae_plus.wireless.WirelessSlaveLink;
import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -18,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(InterfaceLogic.class) @Mixin(InterfaceLogic.class)
public abstract class InterfaceLogicChannelCardMixin implements InterfaceWirelessLinkBridge { public abstract class InterfaceLogicChannelCardMixin implements IInterfaceWirelessLinkBridge {
@Shadow(remap = false) public abstract IUpgradeInventory getUpgrades(); @Shadow(remap = false) public abstract IUpgradeInventory getUpgrades();
@ -107,10 +106,12 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
try { try {
long channel = 0L; long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false; boolean found = false;
for (ItemStack stack : getUpgrades()) { for (ItemStack stack : getUpgrades()) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack); channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true; found = true;
break; break;
} }
@ -131,6 +132,8 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles
eap$link = new WirelessSlaveLink(endpoint); eap$link = new WirelessSlaveLink(endpoint);
} }
// 设置频道卡的所有者UUID如果有的话
eap$link.setPlacerId(ownerUUID);
eap$link.setFrequency(channel); eap$link.setFrequency(channel);
eap$link.updateStatus(); eap$link.updateStatus();

View File

@ -1,8 +0,0 @@
package com.extendedae_plus.bridge;
/**
* 旧名兼容已迁移到非 mixin 避免 Mixin 处理器禁止直接引用
*/
public interface InterfaceLogicChannelLinkBridge {
void eap$updateWirelessLink();
}

View File

@ -1,8 +1,7 @@
package com.extendedae_plus.mixin.ae2.helpers; package com.extendedae_plus.mixin.ae2.helpers;
import appeng.helpers.InterfaceLogic; import appeng.helpers.InterfaceLogic;
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge;
import com.extendedae_plus.util.ExtendedAELogger;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -29,7 +28,7 @@ public abstract class InterfaceLogicTickerMixin {
return; return;
} }
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) {
// 处理延迟初始化 // 处理延迟初始化
bridge.eap$handleDelayedInit(); bridge.eap$handleDelayedInit();
} }
@ -38,7 +37,7 @@ public abstract class InterfaceLogicTickerMixin {
@Inject(method = "tickingRequest", at = @At("TAIL"), remap = false) @Inject(method = "tickingRequest", at = @At("TAIL"), remap = false)
private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) { CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) {
bridge.eap$updateWirelessLink(); bridge.eap$updateWirelessLink();
} }
} }

View File

@ -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.networking.IManagedGridNode;
import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeInventory;

View File

@ -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;
import appeng.api.crafting.IPatternDetails.IInput; import appeng.api.crafting.IPatternDetails.IInput;
@ -6,7 +6,7 @@ import appeng.api.stacks.AEKey;
import appeng.api.stacks.GenericStack; import appeng.api.stacks.GenericStack;
import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderTarget; 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.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Collections; import java.util.Collections;
@Mixin(value = PatternProviderLogic.class, remap = false) @Mixin(value = PatternProviderLogic.class, remap = false)
public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder { public class PatternProviderLogicAdvancedMixin implements IAdvancedBlocking {
@Unique @Unique
private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking"; private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking";

View File

@ -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.api.crafting.IPatternDetails;
import appeng.crafting.pattern.AEProcessingPattern; import appeng.crafting.pattern.AEProcessingPattern;
import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogic;
import com.extendedae_plus.api.SmartDoublingAwarePattern; import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern;
import com.extendedae_plus.api.SmartDoublingHolder; import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder;
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicPatternsAccessor; import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = PatternProviderLogic.class, remap = false) import static com.extendedae_plus.util.smartDoubling.PatternScaler.getComputedMul;
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Unique
private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling";
@Unique @Mixin(value = PatternProviderLogic.class, remap = false)
private boolean eap$smartDoubling = 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 @Override
public boolean eap$getSmartDoubling() { public boolean eap$getSmartDoubling() {
@ -31,23 +33,31 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Override @Override
public void eap$setSmartDoubling(boolean value) { public void eap$setSmartDoubling(boolean value) {
this.eap$smartDoubling = value; this.eap$smartDoubling = value;
// 立即将开关状态应用到当前 Provider 的样板上避免等待下一次 updatePatterns // 触发一次刷新
try { 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(); ((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")) @Inject(method = "writeToNBT", at = @At("TAIL"))
private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) { private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) {
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
// 保存供应器级别上限
tag.putInt(EAP_PROVIDER_SCALING_LIMIT, this.eap$providerScalingLimit);
} }
@Inject(method = "readFromNBT", at = @At("TAIL")) @Inject(method = "readFromNBT", at = @At("TAIL"))
@ -55,20 +65,24 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
if (tag.contains(EAP_SMART_DOUBLING_KEY)) { if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
this.eap$smartDoubling = tag.getBoolean(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")) @Inject(method = "updatePatterns", at = @At("TAIL"))
private void eap$applySmartDoublingToPatterns(CallbackInfo ci) { private void eap$applySmartDoublingToPatterns(CallbackInfo ci) {
try { try {
var list = ((PatternProviderLogicPatternsAccessor) this).eap$patterns(); var list = ((PatternProviderLogicAccessor) this).eap$patterns();
boolean allow = this.eap$smartDoubling; boolean allow = this.eap$smartDoubling;
int limit = this.eap$providerScalingLimit;
for (IPatternDetails details : list) { for (IPatternDetails details : list) {
if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) { if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern pattern) {
aware.eap$setAllowScaling(allow); pattern.eap$setAllowScaling(allow);
pattern.eap$setMultiplierLimit(getComputedMul(proc, limit));
} }
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {}
}
} }
@Shadow @Shadow
@ -77,6 +91,7 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
private void onExportSettings(CompoundTag output, CallbackInfo ci) { private void onExportSettings(CompoundTag output, CallbackInfo ci) {
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); 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")) @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 // 持久化到 world
this.saveChanges(); this.saveChanges();
} }
if (input.contains(EAP_PROVIDER_SCALING_LIMIT)) {
this.eap$providerScalingLimit = input.getInt(EAP_PROVIDER_SCALING_LIMIT);
this.saveChanges();
}
} }
} }

View File

@ -1,7 +1,7 @@
package com.extendedae_plus.mixin.ae2.helpers.patternprovider; package com.extendedae_plus.mixin.ae2.helpers.patternprovider;
import appeng.helpers.patternprovider.PatternProviderLogic; 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.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -27,7 +27,7 @@ public abstract class PatternProviderLogicTickerMixin {
if (node != null && node.getLevel() != null && node.getLevel().isClientSide) { if (node != null && node.getLevel() != null && node.getLevel().isClientSide) {
return; return;
} }
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) {
bridge.eap$handleDelayedInit(); bridge.eap$handleDelayedInit();
} }
} }
@ -35,7 +35,7 @@ public abstract class PatternProviderLogicTickerMixin {
@Inject(method = "tickingRequest", at = @At("TAIL")) @Inject(method = "tickingRequest", at = @At("TAIL"))
private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) { CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { if (this$0 instanceof IInterfaceWirelessLinkBridge bridge) {
bridge.eap$updateWirelessLink(); bridge.eap$updateWirelessLink();
} }
} }

View File

@ -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<Boolean> cir) {
try {
if (!input.contains("upgrades")) {
cir.setReturnValue(false);
return;
}
CompoundTag desiredUpgradesTag = input.getCompound("upgrades");
InternalInventory upgrades = upgradeableObject.getUpgrades();
// 收集背包和网络工具作为升级卡来源
var upgradeSources = new ArrayList<InternalInventory>();
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);
}
}
}

View File

@ -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.IPartHost;
import appeng.api.parts.SelectedPart; import appeng.api.parts.SelectedPart;
@ -36,7 +36,7 @@ import java.lang.reflect.Method;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; import static com.extendedae_plus.util.Logger.EAP$LOGGER;
/** /**
* AE2 石英切割刀添加潜行 + 右键 指向世界中的方块/部件如线缆复制其名称到剪贴板 * AE2 石英切割刀添加潜行 + 右键 指向世界中的方块/部件如线缆复制其名称到剪贴板
@ -198,7 +198,7 @@ public abstract class QuartzCuttingKnifeItemMixin {
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
return null; // GTCEu 不可用 return null; // GTCEu 不可用
} catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) { } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | java.lang.reflect.InvocationTargetException e) {
LOGGER.error("处理 GTCEu 配方翻译失败: {}", e.getMessage()); EAP$LOGGER.error("处理 GTCEu 配方翻译失败: {}", e.getMessage());
return null; // 反射失败 return null; // 反射失败
} }
return null; // GTCEu 方块实体 return null; // GTCEu 方块实体
@ -247,7 +247,7 @@ public abstract class QuartzCuttingKnifeItemMixin {
try { try {
latch.await(); latch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("剪贴板复制线程中断: {}", e.getMessage()); EAP$LOGGER.error("剪贴板复制线程中断: {}", e.getMessage());
} }
return result.get(); return result.get();
} else { } else {

View File

@ -5,8 +5,8 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.guisync.GuiSync; import appeng.menu.guisync.GuiSync;
import appeng.menu.implementations.PatternProviderMenu; import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.api.AdvancedBlockingHolder; import com.extendedae_plus.api.IAdvancedBlocking;
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync; import com.extendedae_plus.api.IPatternProviderMenuAdvancedSync;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import org.spongepowered.asm.mixin.Final; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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) @Mixin(PatternProviderMenu.class)
public abstract class PatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync { public abstract class PatternProviderMenuAdvancedMixin implements IPatternProviderMenuAdvancedSync {
@Final @Final
@Shadow(remap = false) @Shadow(remap = false)
protected PatternProviderLogic logic; protected PatternProviderLogic logic;
@ -35,7 +35,7 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
// 避免@Shadow父类方法改用公共APIAEBaseMenu#isClientSide() // 避免@Shadow父类方法改用公共APIAEBaseMenu#isClientSide()
if (!((AEBaseMenu) (Object) this).isClientSide()) { if (!((AEBaseMenu) (Object) this).isClientSide()) {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); 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) { private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
} }
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
@ -57,11 +57,11 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
private void eap$initAdvancedSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { private void eap$initAdvancedSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof AdvancedBlockingHolder holder) { if (l instanceof IAdvancedBlocking holder) {
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking(); this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
} }
} catch (Throwable t) { } catch (Throwable t) {
LOGGER.error("Error initializing advanced sync", t); EAP$LOGGER.error("Error initializing advanced sync", t);
} }
} }

View File

@ -5,8 +5,8 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.guisync.GuiSync; import appeng.menu.guisync.GuiSync;
import appeng.menu.implementations.PatternProviderMenu; import appeng.menu.implementations.PatternProviderMenu;
import com.extendedae_plus.api.PatternProviderMenuDoublingSync; import com.extendedae_plus.api.smartDoubling.IPatternProviderMenuDoublingSync;
import com.extendedae_plus.api.SmartDoublingHolder; import com.extendedae_plus.api.smartDoubling.ISmartDoublingHolder;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import org.spongepowered.asm.mixin.Final; 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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 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) @Mixin(PatternProviderMenu.class)
public abstract class PatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync { public abstract class PatternProviderMenuDoublingMixin implements IPatternProviderMenuDoublingSync {
@Final @Final
@Shadow(remap = false) @Shadow(remap = false)
protected PatternProviderLogic logic; protected PatternProviderLogic logic;
@ -28,13 +28,17 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
@Unique @Unique
@GuiSync(21) @GuiSync(21)
public boolean eap$SmartDoubling = false; public boolean eap$SmartDoubling = false;
@Unique
@GuiSync(22)
public int eap$PerProviderScalingLimit = 0; // 0 = no limit
@Inject(method = "broadcastChanges", at = @At("HEAD")) @Inject(method = "broadcastChanges", at = @At("HEAD"))
private void eap$syncSmartDoubling(CallbackInfo ci) { private void eap$syncSmartDoubling(CallbackInfo ci) {
if (!((AEBaseMenu) (Object) this).isClientSide()) { if (!((AEBaseMenu) (Object) this).isClientSide()) {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); 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) { private void eap$initSmartSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); this.eap$SmartDoubling = holder.eap$getSmartDoubling();
this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit();
} }
} catch (Throwable t) { } 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<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { private void eap$initSmartSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
try { try {
var l = this.logic; var l = this.logic;
if (l instanceof SmartDoublingHolder holder) { if (l instanceof ISmartDoublingHolder holder) {
this.eap$SmartDoubling = holder.eap$getSmartDoubling(); this.eap$SmartDoubling = holder.eap$getSmartDoubling();
this.eap$PerProviderScalingLimit = holder.eap$getProviderSmartDoublingLimit();
} }
} catch (Throwable t) { } catch (Throwable t) {
LOGGER.error("Error initializing smart doubling sync", t); EAP$LOGGER.error("Error initializing smart doubling sync", t);
} }
} }
@Override @Override
public boolean eap$getSmartDoublingSynced() { public boolean eap$getSmartDoublingSynced() {
return this.eap$SmartDoubling; return this.eap$SmartDoubling;
} }
@Override
public int eap$getScalingLimit() {
return this.eap$PerProviderScalingLimit;
}
} }

View File

@ -7,7 +7,7 @@ import appeng.helpers.patternprovider.PatternProviderLogicHost;
import appeng.menu.AEBaseMenu; import appeng.menu.AEBaseMenu;
import appeng.menu.ToolboxMenu; import appeng.menu.ToolboxMenu;
import appeng.menu.implementations.PatternProviderMenu; 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 com.extendedae_plus.compat.UpgradeSlotCompat;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;

View File

@ -1,7 +1,7 @@
package com.extendedae_plus.mixin.ae2.parts; package com.extendedae_plus.mixin.ae2.parts;
import appeng.parts.AEBasePart; import appeng.parts.AEBasePart;
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.api.bridge.IInterfaceWirelessLinkBridge;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -18,8 +18,8 @@ public class AEBasePartClientSyncMixin {
@Inject(method = "writeToStream", at = @At("TAIL")) @Inject(method = "writeToStream", at = @At("TAIL"))
private void eap$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) { private void eap$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) {
// 检查是否实现了无线链接桥接接口 // 检查是否实现了无线链接桥接接口
if (this instanceof InterfaceWirelessLinkBridge) { if (this instanceof IInterfaceWirelessLinkBridge) {
InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; IInterfaceWirelessLinkBridge bridge = (IInterfaceWirelessLinkBridge) this;
// 同步无线连接状态到客户端 // 同步无线连接状态到客户端
boolean connected = false; boolean connected = false;
try { try {
@ -44,8 +44,8 @@ public class AEBasePartClientSyncMixin {
boolean connected = data.readBoolean(); boolean connected = data.readBoolean();
// 检查是否实现了无线链接桥接接口 // 检查是否实现了无线链接桥接接口
if (this instanceof InterfaceWirelessLinkBridge) { if (this instanceof IInterfaceWirelessLinkBridge) {
InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; IInterfaceWirelessLinkBridge bridge = (IInterfaceWirelessLinkBridge) this;
try { try {
// 更新客户端状态 // 更新客户端状态
bridge.eap$setClientWirelessState(connected); bridge.eap$setClientWirelessState(connected);

View File

@ -3,16 +3,14 @@ package com.extendedae_plus.mixin.ae2.parts.automation;
import appeng.api.networking.security.IActionHost; import appeng.api.networking.security.IActionHost;
import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeInventory;
import appeng.api.upgrades.IUpgradeableObject; import appeng.api.upgrades.IUpgradeableObject;
import appeng.helpers.InterfaceLogicHost;
import appeng.parts.automation.IOBusPart; 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.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.init.ModItems;
import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.util.Logger;
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -23,7 +21,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* AE2 I/O 总线注入频道卡联动在升级变更时读取频道并更新无线链接 * AE2 I/O 总线注入频道卡联动在升级变更时读取频道并更新无线链接
*/ */
@Mixin(value = IOBusPart.class, remap = false) @Mixin(value = IOBusPart.class, remap = false)
public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { public abstract class IOBusPartChannelCardMixin implements IInterfaceWirelessLinkBridge, IUpgradeableObject {
@Unique @Unique
private WirelessSlaveLink eap$link; private WirelessSlaveLink eap$link;
@ -73,10 +71,12 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink
try { try {
IUpgradeInventory inv = this.getUpgrades(); IUpgradeInventory inv = this.getUpgrades();
long channel = 0L; long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false; boolean found = false;
for (var stack : inv) { for (var stack : inv) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack); channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true; found = true;
break; break;
} }
@ -88,14 +88,14 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink
} }
eap$lastChannel = channel; eap$lastChannel = channel;
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel); Logger.EAP$LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel);
if (!found) { if (!found) {
// 无频道卡则断开 // 无频道卡则断开
if (eap$link != null) { if (eap$link != null) {
eap$link.setFrequency(0L); eap$link.setFrequency(0L);
eap$link.updateStatus(); eap$link.updateStatus();
ExtendedAELogger.LOGGER.debug("[服务端] IOBus 断开频道链接"); Logger.EAP$LOGGER.debug("[服务端] IOBus 断开频道链接");
// 立即通知客户端状态变化断开连接无需延迟 // 立即通知客户端状态变化断开连接无需延迟
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
} }
@ -108,17 +108,19 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink
() -> ((IActionHost)(Object)this).getActionableNode() () -> ((IActionHost)(Object)this).getActionableNode()
); );
eap$link = new WirelessSlaveLink(endpoint); 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.setFrequency(channel);
eap$link.updateStatus(); 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(); ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("[服务端] IOBus 初始化频道链接失败", e); Logger.EAP$LOGGER.error("[服务端] IOBus 初始化频道链接失败", e);
} }
} }

View File

@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.parts.automation;
import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNode;
import appeng.api.networking.ticking.TickRateModulation; import appeng.api.networking.ticking.TickRateModulation;
import appeng.parts.automation.IOBusPart; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
@ -17,7 +17,7 @@ public abstract class IOBusPartTickerChannelCardMixin {
@Inject(method = "tickingRequest", at = @At("TAIL")) @Inject(method = "tickingRequest", at = @At("TAIL"))
private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) { private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) {
if (this instanceof InterfaceWirelessLinkBridge bridge) { if (this instanceof IInterfaceWirelessLinkBridge bridge) {
bridge.eap$updateWirelessLink(); bridge.eap$updateWirelessLink();
} }
} }

View File

@ -5,14 +5,13 @@ import appeng.api.networking.security.IActionHost;
import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeInventory;
import appeng.api.upgrades.IUpgradeableObject; import appeng.api.upgrades.IUpgradeableObject;
import appeng.parts.storagebus.StorageBusPart; 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.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.init.ModItems;
import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.util.Logger;
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; import net.minecraft.nbt.CompoundTag;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -23,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* AE2 的存储总线注入频道卡联动在升级变更时读取频道并更新无线链接 * AE2 的存储总线注入频道卡联动在升级变更时读取频道并更新无线链接
*/ */
@Mixin(value = StorageBusPart.class, remap = false) @Mixin(value = StorageBusPart.class, remap = false)
public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { public abstract class StorageBusPartChannelCardMixin implements IInterfaceWirelessLinkBridge, IUpgradeableObject {
@Unique @Unique
private WirelessSlaveLink eap$link; private WirelessSlaveLink eap$link;
@ -70,10 +69,12 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles
try { try {
IUpgradeInventory inv = this.getUpgrades(); IUpgradeInventory inv = this.getUpgrades();
long channel = 0L; long channel = 0L;
java.util.UUID ownerUUID = null;
boolean found = false; boolean found = false;
for (var stack : inv) { for (var stack : inv) {
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
channel = ChannelCardItem.getChannel(stack); channel = ChannelCardItem.getChannel(stack);
ownerUUID = ChannelCardItem.getOwnerUUID(stack);
found = true; found = true;
break; break;
} }
@ -85,13 +86,13 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles
} }
eap$lastChannel = channel; eap$lastChannel = channel;
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel); Logger.EAP$LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel);
if (!found) { if (!found) {
if (eap$link != null) { if (eap$link != null) {
eap$link.setFrequency(0L); eap$link.setFrequency(0L);
eap$link.updateStatus(); eap$link.updateStatus();
ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 断开频道链接"); Logger.EAP$LOGGER.debug("[服务端] StorageBus 断开频道链接");
// 通知客户端状态变化 // 通知客户端状态变化
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
} }
@ -104,17 +105,19 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles
() -> ((IActionHost)(Object)this).getActionableNode() () -> ((IActionHost)(Object)this).getActionableNode()
); );
eap$link = new WirelessSlaveLink(endpoint); 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.setFrequency(channel);
eap$link.updateStatus(); 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(); ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
} catch (Exception e) { } catch (Exception e) {
ExtendedAELogger.LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e); Logger.EAP$LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e);
} }
} }

View File

@ -3,7 +3,7 @@ package com.extendedae_plus.mixin.ae2.parts.storagebus;
import appeng.api.networking.IGridNode; import appeng.api.networking.IGridNode;
import appeng.api.networking.ticking.TickRateModulation; import appeng.api.networking.ticking.TickRateModulation;
import appeng.parts.storagebus.StorageBusPart; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
@ -17,7 +17,7 @@ public abstract class StorageBusPartTickerChannelCardMixin {
@Inject(method = "tickingRequest", at = @At("TAIL")) @Inject(method = "tickingRequest", at = @At("TAIL"))
private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) { private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable<TickRateModulation> cir) {
if (this instanceof InterfaceWirelessLinkBridge bridge) { if (this instanceof IInterfaceWirelessLinkBridge bridge) {
bridge.eap$updateWirelessLink(); bridge.eap$updateWirelessLink();
} }
} }

View File

@ -6,8 +6,8 @@ import appeng.api.upgrades.UpgradeInventories;
import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogic;
import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.helpers.patternprovider.PatternProviderLogicHost;
import com.extendedae_plus.compat.UpgradeSlotCompat; import com.extendedae_plus.compat.UpgradeSlotCompat;
import com.extendedae_plus.util.Logger;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@ -28,10 +28,10 @@ public class AppfluxPatternProviderLogicMixin {
at = @At("TAIL")) at = @At("TAIL"))
private void eap$modifyAppfluxUpgradeSlots(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { private void eap$modifyAppfluxUpgradeSlots(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) {
try { try {
// 只有当appflux存在且不启用我们的升级槽时才修改数量 // 只有当appflux存在且不启用我们的升级槽时才修改数量
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && UpgradeSlotCompat.shouldEnableChannelCard()) { if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && UpgradeSlotCompat.shouldEnableChannelCard()) {
// 使用反射找到appflux的升级槽字段并替换 // 使用反射找到appflux的升级槽字段并替换
try { try {
Field upgradesField = this.getClass().getDeclaredField("af_$upgrades"); Field upgradesField = this.getClass().getDeclaredField("af_$upgrades");
@ -39,7 +39,7 @@ public class AppfluxPatternProviderLogicMixin {
IUpgradeInventory currentUpgrades = (IUpgradeInventory) upgradesField.get(this); IUpgradeInventory currentUpgrades = (IUpgradeInventory) upgradesField.get(this);
if (currentUpgrades != null) { if (currentUpgrades != null) {
// 创建新的2槽升级槽 // 创建新的2槽升级槽
IUpgradeInventory newUpgrades = UpgradeInventories.forMachine( IUpgradeInventory newUpgrades = UpgradeInventories.forMachine(
host.getTerminalIcon().getItem(), host.getTerminalIcon().getItem(),
@ -49,7 +49,7 @@ public class AppfluxPatternProviderLogicMixin {
// 调用appflux的升级变更方法 // 调用appflux的升级变更方法
this.getClass().getDeclaredMethod("af_$onUpgradesChanged").invoke(this); this.getClass().getDeclaredMethod("af_$onUpgradesChanged").invoke(this);
} catch (Exception e) { } 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); upgradesField.set(this, newUpgrades);
} }
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("反射修改appflux升级槽失败", e); Logger.EAP$LOGGER.error("反射修改appflux升级槽失败", e);
} }
} }
} catch (Exception e) { } catch (Exception e) {
com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e); Logger.EAP$LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e);
} }
} }
} }

View File

@ -4,8 +4,9 @@ import appeng.client.gui.Icon;
import appeng.client.gui.implementations.PatternProviderScreen; import appeng.client.gui.implementations.PatternProviderScreen;
import appeng.client.gui.style.ScreenStyle; import appeng.client.gui.style.ScreenStyle;
import appeng.menu.SlotSemantics; import appeng.menu.SlotSemantics;
import com.extendedae_plus.NewIcon; import com.extendedae_plus.ae.client.gui.NewIcon;
import com.extendedae_plus.api.ExPatternButtonsAccessor; import com.extendedae_plus.api.IExPatternButtonsAccessor;
import com.extendedae_plus.api.IExPatternPageAccessor;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import com.glodblock.github.extendedae.client.button.ActionEPPButton; import com.glodblock.github.extendedae.client.button.ActionEPPButton;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider; 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.Field;
import java.lang.reflect.Method; 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) @Mixin(GuiExPatternProvider.class)
public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<ContainerExPatternProvider> implements ExPatternButtonsAccessor, com.extendedae_plus.api.ExPatternPageAccessor { public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<ContainerExPatternProvider> implements IExPatternButtonsAccessor, IExPatternPageAccessor {
@Unique @Unique
ScreenStyle eap$screenStyle; ScreenStyle eap$screenStyle;

View File

@ -11,7 +11,7 @@ import appeng.menu.AEBaseMenu;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.init.ModNetwork; import com.extendedae_plus.init.ModNetwork;
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; 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.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal; import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;

View File

@ -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<NetworkEvent.Context> 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);
}
}

View File

@ -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<NetworkEvent.Context> ctxSupplier) {
var ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
try {
// 在客户端执行高亮
ResourceKey<Level> 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);
}
}

Some files were not shown because too many files have changed in this diff Show More