Merge branch 'develop/fix' into develop/gtceu_support
# Conflicts: # gradle.properties # src/main/java/com/extendedae_plus/mixin/ae2/client/gui/AEBaseScreenMixin.java
This commit is contained in:
commit
f613ffcf72
51
CHANGELOG.md
51
CHANGELOG.md
|
|
@ -1,6 +1,55 @@
|
|||
# Changelog
|
||||
|
||||
## [Unreleased] / [未发布]
|
||||
## [Unreleased]
|
||||
|
||||
## [1.4.2]
|
||||
### Added / 新增
|
||||
- 添加实体加速器,最高可加速 1024 倍(配置文件可配置能耗、黑名单、额外消耗倍率名单)
|
||||
- Added Entity Accelerator block, capable of up to 1024× acceleration (configurable energy cost, blacklist, and extra consumption multiplier list).
|
||||
- 添加实体加速卡系列,用于设置实体加速器加速倍率
|
||||
- Added Entity Acceleration Card series to configure acceleration multiplier for the Entity Accelerator.
|
||||
- 添加物品“吞噬万籁的寂静”,可存储 21 亿种不同的资源,每种资源存储数量无上限(气体等物品须安装对应附属 mod)
|
||||
- Added item “Devourer of Cosmic Silence”: stores up to 2.1 billion distinct resources with unlimited quantity per type (gases and other types require respective addon mods).
|
||||
- eae扩展样板管理界面添加F键搜索支持
|
||||
- Added F-key search support in Extended Pattern Management Terminal.
|
||||
- 合成计划界面支持 Shift+点击取消自动添加缺失材料至 JEI 书签
|
||||
- Crafting Plan GUI: Shift-clicking cancel button auto-adds missing ingredients to JEI bookmarks.
|
||||
- AE 合成暂停检查阈值配置项(默认值 100000)
|
||||
- Added config option for AE crafting pause threshold (default: 100000).
|
||||
- 智能系列支持高级 AE 供应器
|
||||
- Smart series now supports Advanced AE Providers.
|
||||
- 无线收发器支持重命名,Jade 可以在从节点显示主节点名称
|
||||
- Wireless Repeater supports renaming; Jade HUD displays master node name on slave terminals.
|
||||
- 智能阻挡开启时自动启用原版阻挡
|
||||
- Enabling Smart Blocking now also activates Vanilla Blocking automatically.
|
||||
- 调整样板制作数量显示上限
|
||||
- Adjusted display limit for pattern output quantity.
|
||||
- 放宽扩展供应器样板手动倍增限制
|
||||
- Relaxed manual doubling restrictions for Extended Providers.
|
||||
- 为ME接口添加倍增按钮用于倍增标记物品数量
|
||||
- Add a multiplier button to the ME interface for multiplying the marked item quantity
|
||||
### Fixed / 修复
|
||||
- 修复无线收发器频道限制与渲染问题
|
||||
- Fixed channel limit and rendering issues for Wireless Repeater.
|
||||
|
||||
## [1.4.1]
|
||||
### Added / 新增
|
||||
- 模组配置项:可设置智能倍增的最大倍数
|
||||
- Config option: set maximum multiplier for Smart Doubling.
|
||||
- 上传样板搜索框:右键点击可清空文本
|
||||
- Right-clicking the pattern upload search box clears the text.
|
||||
- 合成监控界面:打开样板供应器 UI 时自动跳转到该样板所在页数,并以彩虹高亮显示
|
||||
- Crafting monitor: auto-jumps to the page containing the pattern and highlights it with rainbow effect when opening the provider UI.
|
||||
- 样板供应器 UI 标题现在显示为玩家自定义名称
|
||||
- Pattern Provider UI now displays the player-customized name as its title.
|
||||
- 配置项:当产物数量达到指定值时启用智能倍增
|
||||
- Config option: enable Smart Doubling only when output amount reaches a specified threshold.
|
||||
|
||||
### Fixed / 修复
|
||||
- 修复编码终端中空白配方无法覆盖已有编码样板的问题
|
||||
- Fixed issue where blank recipes couldn't overwrite existing encoded patterns in the Encoding Terminal.
|
||||
- 修复非 AE 与 ExtendedAE 样板供应器无法发配材料的问题
|
||||
- Fixed issue where non-AE2 and non-ExtendedAE pattern providers failed to dispatch materials.
|
||||
|
||||
## [1.4.0-fix]
|
||||
### Added / 新增
|
||||
|
|
|
|||
|
|
@ -41,5 +41,7 @@ ExtendedAE Plus 是一个面向 Applied Energistics 2 与 ExtendedAE 的功能
|
|||
|
||||
- Applied Energistics 2(AE2):MIT License
|
||||
- SpongePowered Mixin:MIT License
|
||||
- Configuration(by Toma):MIT License
|
||||
- AE2Things(by ProjectET):MIT License
|
||||
|
||||
请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id 'dev.architectury.loom' version '1.10-SNAPSHOT'
|
||||
id 'maven-publish'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = project.maven_group
|
||||
|
|
@ -116,6 +117,10 @@ dependencies {
|
|||
modRuntimeOnly "curse.maven:geckolib-388172:6920925"
|
||||
//mclib运行时
|
||||
forgeRuntimeLibrary "com.eliotlash.mclib:mclib:20"
|
||||
|
||||
// compileOnly 供代码编译
|
||||
include "curse.maven:configuration-444699:4710266"
|
||||
modImplementation "curse.maven:configuration-444699:4710266"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,26 @@
|
|||
package com.extendedae_plus;
|
||||
|
||||
import appeng.api.storage.StorageCells;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.init.ModBlockEntities;
|
||||
import com.extendedae_plus.init.ModBlocks;
|
||||
import com.extendedae_plus.init.ModCreativeTabs;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||
import com.extendedae_plus.client.ClientRegistrar;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.*;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import net.minecraftforge.client.ConfigScreenHandler;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ModelEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.level.LevelEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import com.extendedae_plus.client.ClientProxy;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
|
||||
/**
|
||||
* ExtendedAE Plus 主mod类
|
||||
|
|
@ -36,36 +34,43 @@ public class ExtendedAEPlus {
|
|||
|
||||
public ExtendedAEPlus() {
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
||||
// 在客户端尽早注册内置模型,保证首次资源加载前映射已建立(仿照 AE2 的 AppEngClient 构造期注册)
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientProxy::init);
|
||||
|
||||
// 客户端的内置模型注册将在客户端事件阶段执行(见 ClientModEvents),不要在构造器中提前执行
|
||||
|
||||
// 注册mod初始化事件
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
|
||||
|
||||
// 注册方块与方块实体
|
||||
ModBlocks.BLOCKS.register(modEventBus);
|
||||
ModBlockEntities.BLOCK_ENTITY_TYPES.register(modEventBus);
|
||||
ModItems.ITEMS.register(modEventBus);
|
||||
|
||||
// 在注册阶段将创造模式标签页放入注册表
|
||||
ModCreativeTabs.TABS.register(modEventBus);
|
||||
|
||||
ModMenuTypes.MENUS.register(modEventBus);
|
||||
|
||||
|
||||
// 注册到Forge事件总线
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
|
||||
MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
|
||||
// 注册通用配置
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
|
||||
|
||||
// 客户端侧延迟注册:在 FMLClientSetupEvent 阶段执行(包含 MenuScreens 绑定等)
|
||||
modEventBus.addListener((FMLClientSetupEvent e) -> ClientProxy.onClientSetup(e));
|
||||
ModConfig.init();
|
||||
// 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush)
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
|
||||
MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
|
||||
// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通用初始化设置
|
||||
*/
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
|
||||
|
||||
// 注册本模组网络通道与数据包
|
||||
event.enqueueWork(() -> {
|
||||
// 注册升级卡
|
||||
new UpgradeCards(event);
|
||||
ModNetwork.register();
|
||||
// 注册自定义 Curios 宿主定位器,便于将菜单宿主信息在服务端与客户端间同步
|
||||
MenuLocators.register(CuriosItemLocator.class, CuriosItemLocator::writeToPacket, CuriosItemLocator::readFromPacket);
|
||||
|
|
@ -78,4 +83,44 @@ public class ExtendedAEPlus {
|
|||
public static ResourceLocation id(String path) {
|
||||
return new ResourceLocation(MODID, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端专用事件订阅类。
|
||||
* 完成客户端相关的延迟注册操作(如菜单界面绑定、渲染器注册、模型加载等),确保这些操作只在客户端执行,避免服务端崩溃。
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = ExtendedAEPlus.MODID,
|
||||
bus = Mod.EventBusSubscriber.Bus.MOD,
|
||||
value = Dist.CLIENT
|
||||
)
|
||||
public static class ClientModEvents {
|
||||
@SubscribeEvent
|
||||
public static void onClientSetup(final FMLClientSetupEvent event) {
|
||||
// 直接在此处执行客户端一次性注册(UI/屏幕/渲染器绑定)
|
||||
// 注册客户端配置界面
|
||||
// ClientRegistrar.registerConfigScreen();
|
||||
|
||||
// 将 InitScreens 的注册委托给 ClientRegistrar,便于集中管理客户端注册逻辑
|
||||
ClientRegistrar.registerInitScreens();
|
||||
|
||||
// 菜单 -> 屏幕 绑定
|
||||
ClientRegistrar.registerMenuScreens();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterGeometryLoaders(final ModelEvent.RegisterGeometryLoaders evt) {
|
||||
try {
|
||||
ClientRegistrar.initBuiltInModels();
|
||||
// 注册 AE2 部件模型(例如 entity_ticker_part_item),仿照 CrazyAddons 的做法
|
||||
ModItems.registerPartModels();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// 在世界加载时注册/加载 SavedData
|
||||
private static void onLevelLoad(LevelEvent.Load event) {
|
||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
||||
InfinityStorageManager.getForLevel(serverLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.extendedae_plus.ae.api.storage;
|
||||
|
||||
import appeng.api.storage.cells.ICellHandler;
|
||||
import appeng.api.storage.cells.ISaveProvider;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* InfinityBigIntegerCellHandler
|
||||
*
|
||||
* 该类实现 AE2 的 ICellHandler,用于:
|
||||
* - 判定某个 ItemStack 是否为本 mod 的 Infinity 存储单元
|
||||
* - 在 AE2 请求访问或创建存储单元时,创建并返回对应的 StorageCell 实例
|
||||
*/
|
||||
public class InfinityBigIntegerCellHandler implements ICellHandler {
|
||||
|
||||
/** Handler 单例,供注册与调用使用 */
|
||||
public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler();
|
||||
|
||||
/**
|
||||
* 判断给定的 ItemStack 是否为 InfinityBigIntegerCell
|
||||
*/
|
||||
@Override
|
||||
public boolean isCell(ItemStack is) {
|
||||
return is.getItem() instanceof InfinityBigIntegerCellItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AE2 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventory(StorageCell 实现)。
|
||||
* 参数 container 为 AE2 提供的保存回调(ISaveProvider),当 cell 需要持久化时会调用它。
|
||||
*/
|
||||
@Override
|
||||
public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
|
||||
return InfinityBigIntegerCellInventory.createInventory(is, container);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,413 @@
|
|||
package com.extendedae_plus.ae.api.storage;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.networking.security.IActionSource;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.api.stacks.KeyCounter;
|
||||
import appeng.api.storage.cells.CellState;
|
||||
import appeng.api.storage.cells.ISaveProvider;
|
||||
import appeng.api.storage.cells.StorageCell;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import com.extendedae_plus.util.storage.InfinityDataStorage;
|
||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
/**
|
||||
* InfinityBigIntegerCellInventory
|
||||
* <p>
|
||||
* 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。
|
||||
* 主要职责:
|
||||
* - 在内存中维护条目映射 (AEKey -> BigInteger 数量)
|
||||
* - 提供插入/提取/列举/持久化等操作的实现
|
||||
* - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化
|
||||
* <p>
|
||||
* 重要字段:
|
||||
* - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息
|
||||
* - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化
|
||||
* - storedMap: 延迟初始化的内存映射,减少未使用时内存占用
|
||||
* - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描
|
||||
* - isPersisted: 标记内存状态是否已同步到持久层
|
||||
*/
|
||||
public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||
|
||||
// 待持久化队列(用于 debounce:在服务器 tick 中合并持久化)
|
||||
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
|
||||
// 数字格式化对象,保留两位小数(复用以减少对象分配)
|
||||
private static final DecimalFormat DF = new DecimalFormat("#.##");
|
||||
|
||||
// 关联的 ItemStack(含可能的 uuid NBT)
|
||||
private final ItemStack stack;
|
||||
// AE2 提供的保存提供者,用于在容器中批量保存时触发回调
|
||||
private final ISaveProvider container;
|
||||
// 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化)
|
||||
private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
|
||||
// 标记是否已持久化到 SavedData
|
||||
private boolean isPersisted = true;
|
||||
// 缓存的总存储量,避免每次调用进行全表扫描
|
||||
private BigInteger totalStored = BigInteger.ZERO;
|
||||
|
||||
/**
|
||||
* 私有构造器:通过 createInventory 工厂方法调用
|
||||
*
|
||||
* @param stack 关联的物品堆
|
||||
* @param saveProvider AE2 的保存回调(可为 null)
|
||||
*/
|
||||
private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) {
|
||||
this.stack = stack;
|
||||
container = saveProvider;
|
||||
// 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化
|
||||
this.storedMap = null;
|
||||
}
|
||||
|
||||
// 创建存储单元库存实例的静态方法
|
||||
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
|
||||
if (stack.getItem() instanceof InfinityBigIntegerCellItem) {
|
||||
return new InfinityBigIntegerCellInventory(stack, saveProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取全局存储实例
|
||||
private static InfinityStorageManager getStorageInstance() {
|
||||
return InfinityStorageManager.INSTANCE;
|
||||
}
|
||||
|
||||
// 服务器 tick 回调:合并并执行待持久化项
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
// 处理本次 tick 中的全部待持久化项
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在服务器停止时被调用,立即强制持久化队列中的所有实例
|
||||
public static void onServerStopping(ServerStoppingEvent event) {
|
||||
InfinityBigIntegerCellInventory inv;
|
||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
||||
try {
|
||||
if (!inv.isPersisted) {
|
||||
inv.persist();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发)
|
||||
try {
|
||||
var stor = getStorageInstance();
|
||||
if (stor != null) stor.setDirty();
|
||||
} catch (Throwable ignored) {
|
||||
LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 将 BigInteger 格式化为带单位的字符串,保留两位小数
|
||||
public static String formatBigInteger(BigInteger number) {
|
||||
// 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行
|
||||
BigDecimal bd = new BigDecimal(number);
|
||||
BigDecimal thousand = new BigDecimal(1000);
|
||||
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
|
||||
int idx = 0;
|
||||
while (bd.compareTo(thousand) >= 0 && idx < units.length - 1) {
|
||||
bd = bd.divide(thousand, 2, RoundingMode.HALF_UP);
|
||||
idx++;
|
||||
}
|
||||
if (idx == 0) {
|
||||
return bd.setScale(0, RoundingMode.DOWN).toPlainString();
|
||||
}
|
||||
return DF.format(bd.doubleValue()) + units[idx];
|
||||
}
|
||||
|
||||
// 获取当前存储单元的数据存储对象
|
||||
private InfinityDataStorage getCellStorage() {
|
||||
if (this.getUUID() == null) {
|
||||
// 如果没有UUID,返回空存储
|
||||
return InfinityDataStorage.EMPTY;
|
||||
} else {
|
||||
// 否则获取或创建对应UUID的存储
|
||||
return getStorageInstance().getOrCreateCell(getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
// 获取存储单元状态(空/非空)
|
||||
@Override
|
||||
public CellState getStatus() {
|
||||
if (this.getCellStoredMap().isEmpty()) {
|
||||
return CellState.EMPTY;
|
||||
}
|
||||
return CellState.NOT_EMPTY;
|
||||
}
|
||||
|
||||
// 获取存储单元的待机能耗
|
||||
@Override
|
||||
public double getIdleDrain() {
|
||||
return 512;
|
||||
}
|
||||
|
||||
// 获取存储单元的描述(此处返回null,可自定义)
|
||||
@Override
|
||||
public Component getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 判断物品堆栈是否有UUID
|
||||
public boolean hasUUID() {
|
||||
return stack.hasTag() && stack.getOrCreateTag().contains("uuid");
|
||||
}
|
||||
|
||||
// 获取物品堆栈的UUID
|
||||
public UUID getUUID() {
|
||||
if (this.hasUUID())
|
||||
return stack.getOrCreateTag().getUUID("uuid");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取或初始化存储映射
|
||||
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
|
||||
if (storedMap == null) {
|
||||
storedMap = new Object2ObjectOpenHashMap<>();
|
||||
this.loadCellStoredMap();
|
||||
}
|
||||
return storedMap;
|
||||
}
|
||||
|
||||
// 从存储中加载物品映射
|
||||
private void loadCellStoredMap() {
|
||||
boolean corruptedTag = false; // 标记数据是否损坏
|
||||
if (!stack.hasTag()) return;
|
||||
ListTag keys = this.getCellStorage().keys;
|
||||
ListTag amounts = this.getCellStorage().amounts;
|
||||
int len = Math.min(keys.size(), amounts.size());
|
||||
for (int i = 0; i < len; i++) {
|
||||
AEKey key = AEKey.fromTagGeneric(keys.getCompound(i));
|
||||
CompoundTag amtTag = amounts.getCompound(i);
|
||||
try {
|
||||
BigInteger amount;
|
||||
if (amtTag.contains("l")) {
|
||||
long v = amtTag.getLong("l");
|
||||
amount = BigInteger.valueOf(v);
|
||||
} else if (amtTag.contains("s")) {
|
||||
amount = new BigInteger(amtTag.getString("s"));
|
||||
} else {
|
||||
corruptedTag = true;
|
||||
continue;
|
||||
}
|
||||
if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
|
||||
corruptedTag = true;
|
||||
} else {
|
||||
// storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销
|
||||
storedMap.put(key, amount);
|
||||
// 更新缓存的总数
|
||||
totalStored = totalStored.add(amount);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
corruptedTag = true;
|
||||
}
|
||||
}
|
||||
// 如果有损坏,保存修正后的数据
|
||||
if (corruptedTag) {
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
// 标记数据需要保存,并通知容器或直接持久化
|
||||
private void saveChanges() {
|
||||
// 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O
|
||||
isPersisted = false;
|
||||
if (container != null) {
|
||||
// 当存在容器时,优先让容器统一处理持久化
|
||||
container.saveChanges();
|
||||
} else {
|
||||
// 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O
|
||||
if (!PENDING_PERSIST.contains(this)) {
|
||||
PENDING_PERSIST.offer(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有可用的物品堆栈及其数量
|
||||
@Override
|
||||
public void getAvailableStacks(KeyCounter out) {
|
||||
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
Object2ObjectMap<AEKey, BigInteger> map = getCellStoredMap();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
AEKey key = entry.getKey();
|
||||
BigInteger value = entry.getValue();
|
||||
|
||||
// 当前 KeyCounter 中已有的值(long)
|
||||
long existing = out.get(key);
|
||||
|
||||
// 将 existing 与当前 value 做 BigInteger 累加并饱和到 Long.MAX_VALUE
|
||||
BigInteger sum = BigInteger.valueOf(existing).add(value);
|
||||
long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue();
|
||||
|
||||
// KeyCounter 没有 set(key,long) 的统一接口暴露(只有 add/remove),所以先移除已存在的值再设置。
|
||||
// 为避免读取-写入竞争,我们计算出要新增的 delta 并调用 add(key, delta)
|
||||
if (existing == Long.MAX_VALUE) {
|
||||
// 已经饱和,无需再添加
|
||||
continue;
|
||||
}
|
||||
long delta;
|
||||
if (toSet == Long.MAX_VALUE) {
|
||||
delta = Long.MAX_VALUE - existing;
|
||||
} else {
|
||||
delta = toSet - existing;
|
||||
}
|
||||
if (delta != 0) {
|
||||
out.add(key, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 持久化存储单元数据到全局存储
|
||||
@Override
|
||||
public void persist() {
|
||||
if (this.isPersisted)
|
||||
return;
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
if (map.isEmpty()) {
|
||||
// 如果存储为空,移除UUID和全局存储中的数据
|
||||
if (this.hasUUID()) {
|
||||
getStorageInstance().removeCell(getUUID());
|
||||
if (stack.getTag() != null) {
|
||||
stack.getTag().remove("uuid");
|
||||
// 移除缓存的 total 字段
|
||||
stack.getTag().remove("total");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 构建要保存的Key和数量列表(混合表示:long 或 string)
|
||||
ListTag amountTags = new ListTag();
|
||||
ListTag keys = new ListTag();
|
||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
||||
BigInteger amount = entry.getValue();
|
||||
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
||||
keys.add(entry.getKey().toTagGeneric());
|
||||
CompoundTag amt = new CompoundTag();
|
||||
if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
amt.putLong("l", amount.longValue());
|
||||
} else {
|
||||
amt.putString("s", amount.toString());
|
||||
}
|
||||
amountTags.add(amt);
|
||||
}
|
||||
}
|
||||
// 如果没有Key,更新为空存储,否则保存数据
|
||||
if (keys.isEmpty()) {
|
||||
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
|
||||
} else {
|
||||
// amounts 现在为 CompoundTag 列表
|
||||
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags);
|
||||
}
|
||||
// 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long
|
||||
if (stack.getOrCreateTag() != null) {
|
||||
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
||||
stack.getOrCreateTag().putLong("total", totalStored.longValue());
|
||||
} else {
|
||||
stack.getOrCreateTag().putString("total", totalStored.toString());
|
||||
}
|
||||
// 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示
|
||||
int typesCount = this.getCellStoredMap().size();
|
||||
stack.getOrCreateTag().putInt("types", typesCount);
|
||||
}
|
||||
isPersisted = true;
|
||||
}
|
||||
|
||||
// 插入物品到存储单元
|
||||
@Override
|
||||
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||
// 数量为0或类型不匹配直接返回
|
||||
if (amount == 0)
|
||||
return 0;
|
||||
// 不允许存储无限单元自身
|
||||
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
|
||||
return 0;
|
||||
// 如果没有UUID,生成UUID并初始化存储
|
||||
if (!this.hasUUID()) {
|
||||
stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
|
||||
getStorageInstance().getOrCreateCell(getUUID());
|
||||
// 确保 storedMap 初始化并从持久层加载数据
|
||||
this.getCellStoredMap();
|
||||
}
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
||||
if (mode == Actionable.MODULATE) {
|
||||
// 实际插入,更新数量并保存
|
||||
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
|
||||
map.put(what, newAmount);
|
||||
// 更新 cached total
|
||||
totalStored = totalStored.add(BigInteger.valueOf(amount));
|
||||
this.saveChanges();
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
// 从存储单元提取物品
|
||||
@Override
|
||||
public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
||||
if (currentAmount.compareTo(BigInteger.ZERO) > 0) {
|
||||
BigInteger requested = BigInteger.valueOf(amount);
|
||||
if (currentAmount.compareTo(requested) <= 0) {
|
||||
// 提取全部
|
||||
long ret;
|
||||
if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
|
||||
ret = Long.MAX_VALUE;
|
||||
} else {
|
||||
ret = currentAmount.longValue();
|
||||
}
|
||||
if (mode == Actionable.MODULATE) {
|
||||
map.remove(what);
|
||||
// 更新 cached total
|
||||
// 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数
|
||||
totalStored = totalStored.subtract(currentAmount);
|
||||
this.saveChanges();
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
// 提取部分
|
||||
if (mode == Actionable.MODULATE) {
|
||||
map.put(what, currentAmount.subtract(requested));
|
||||
// 更新 cached total
|
||||
totalStored = totalStored.subtract(requested);
|
||||
this.saveChanges();
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 获取存储单元内所有物品的总数量(格式化字符串)
|
||||
public String getTotalStorage() {
|
||||
// 使用缓存的 totalStored,避免每次全表扫描
|
||||
return formatBigInteger(totalStored);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.extendedae_plus.ae.definitions.upgrades;
|
||||
|
||||
import appeng.items.materials.UpgradeCardItem;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单一的实体加速卡 Item,通过 ItemStack 的 NBT 存储 exponent(0/1/2/3)来区分等级
|
||||
*/
|
||||
public class EntitySpeedCardItem extends UpgradeCardItem {
|
||||
public static final String NBT_MULTIPLIER = "EAS:mult";
|
||||
|
||||
public EntitySpeedCardItem(Properties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public static ItemStack withMultiplier(int multiplier) {
|
||||
ItemStack s = new ItemStack(ModItems.ENTITY_SPEED_CARD.get());
|
||||
CompoundTag t = s.getOrCreateTag();
|
||||
t.putInt(NBT_MULTIPLIER, multiplier);
|
||||
s.setTag(t);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static int readMultiplier(ItemStack stack) {
|
||||
if (stack == null || stack.isEmpty()) return 1;
|
||||
CompoundTag t = stack.getTag();
|
||||
if (t == null) return 1;
|
||||
int v = t.getInt(NBT_MULTIPLIER);
|
||||
return v <= 0 ? 1 : v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component getName(@NotNull ItemStack stack) {
|
||||
int mult = readMultiplier(stack);
|
||||
String key;
|
||||
switch (mult) {
|
||||
case 2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
|
||||
case 4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
|
||||
case 8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
|
||||
case 16 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x16";
|
||||
default -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x1";
|
||||
}
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
|
||||
public List<Component> getTooltipLines(ItemStack stack) {
|
||||
int mult = readMultiplier(stack);
|
||||
long cap = 1L;
|
||||
switch (mult) {
|
||||
case 16 -> cap = 1024L;
|
||||
case 8 -> cap = 256L;
|
||||
case 4 -> cap = 64L;
|
||||
case 2 -> cap = 8L;
|
||||
}
|
||||
MutableComponent line1 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.multiplier", "x" + mult);
|
||||
MutableComponent line2 = Component.translatable("tooltip." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.max", cap);
|
||||
return List.of(line1, line2);
|
||||
}
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, Level level, List<Component> lines, TooltipFlag flag) {
|
||||
super.appendHoverText(stack, level, lines, flag);
|
||||
// add our custom tooltip lines (multiplier and max)
|
||||
lines.addAll(this.getTooltipLines(stack));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.extendedae_plus.ae.items;
|
||||
|
||||
import appeng.items.parts.PartItem;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import java.util.List;
|
||||
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||
|
||||
|
||||
public class EntitySpeedTickerPartItem extends PartItem<EntitySpeedTickerPart> {
|
||||
public EntitySpeedTickerPartItem(Properties properties) {
|
||||
super(properties, EntitySpeedTickerPart.class, EntitySpeedTickerPart::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> tooltip, TooltipFlag flag) {
|
||||
super.appendHoverText(stack, world, tooltip, flag);
|
||||
// 中文 tooltip:提示需要放入实体加速卡开始加速,显示最大加速倍率和能量消耗说明
|
||||
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.requirement", "需要放入实体加速卡以启用加速"));
|
||||
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.max", "最高可达 1024x 加速"));
|
||||
tooltip.add(Component.translatable("item.extendedae_plus.entity_speed_ticker.tip.energy", "加速将消耗 AE 网络能量,网络能量不足时无法加速"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package com.extendedae_plus.ae.items;
|
||||
|
||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||
import com.google.common.base.Preconditions;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.LongTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
public class InfinityBigIntegerCellItem extends Item {
|
||||
|
||||
public InfinityBigIntegerCellItem() {
|
||||
super(new Properties().stacksTo(1).fireResistant());
|
||||
}
|
||||
|
||||
/**
|
||||
* 在物品悬停提示中展示额外信息。
|
||||
* 功能:
|
||||
* - 若 ItemStack 的 NBT 含有 UUID,则显示该 UUID(不会触发服务器加载或持久化行为)
|
||||
* - 若 NBT 同步了 total 字段,则读取并格式化显示总存储量(使用 Inventory 的 formatBigInteger)
|
||||
*
|
||||
* 设计说明:客户端 tooltip 不主动访问服务端 SavedData,以避免不必要的 I/O 与状态变更。
|
||||
*/
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack,
|
||||
@Nullable Level world,
|
||||
@NotNull List<Component> tooltip,
|
||||
@NotNull TooltipFlag context) {
|
||||
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
|
||||
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
|
||||
|
||||
Preconditions.checkArgument(stack.getItem() == this);
|
||||
// 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
|
||||
CompoundTag tag = stack.getTag();
|
||||
if (tag != null && tag.contains("uuid")) {
|
||||
String uuidStr = tag.getUUID("uuid").toString();
|
||||
tooltip.add(
|
||||
Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
|
||||
);
|
||||
// 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey
|
||||
if (tag.contains("types")) {
|
||||
try {
|
||||
int types = tag.getInt("types");
|
||||
tooltip.add(
|
||||
Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
|
||||
);
|
||||
} catch (Exception ignored) {
|
||||
// ignore malformed value
|
||||
}
|
||||
}
|
||||
// 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位
|
||||
if (tag.contains("total")) {
|
||||
BigInteger total = BigInteger.ZERO;
|
||||
Tag t = tag.get("total");
|
||||
try {
|
||||
if (t instanceof LongTag) {
|
||||
total = BigInteger.valueOf(tag.getLong("total"));
|
||||
} else {
|
||||
String s = tag.getString("total");
|
||||
total = new BigInteger(s);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 解析失败保持为 0
|
||||
}
|
||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||
tooltip.add(
|
||||
Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.extendedae_plus.ae.menu;
|
||||
|
||||
import appeng.core.definitions.AEItems;
|
||||
import appeng.menu.guisync.GuiSync;
|
||||
import appeng.menu.implementations.UpgradeableMenu;
|
||||
import appeng.menu.slot.OptionalFakeSlot;
|
||||
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.util.ConfigParsingUtils;
|
||||
import com.extendedae_plus.util.PowerUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// 实体加速器菜单,负责与客户端界面同步数据
|
||||
public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart> {
|
||||
@GuiSync(716) public boolean accelerateEnabled = true;
|
||||
// 已安装的实体加速卡数量(用于能耗计算)
|
||||
@GuiSync(717) public int entitySpeedCardCount;
|
||||
// 已安装的能量卡数量
|
||||
@GuiSync(718) public int energyCardCount;
|
||||
// 当前生效的配置倍率(从配置中读取并同步)
|
||||
// 当前计算出的生效速度(product of multipliers),同步给客户端用于显示
|
||||
@GuiSync(719) public int effectiveSpeed = 1;
|
||||
@GuiSync(720) public double multiplier = 1.0;
|
||||
@GuiSync(721) public boolean targetBlacklisted = false;
|
||||
|
||||
public boolean getAccelerateEnabled() {
|
||||
return this.accelerateEnabled;
|
||||
}
|
||||
|
||||
public void setAccelerateEnabled(boolean enabled) {
|
||||
this.accelerateEnabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
// 构造方法,初始化菜单并与部件绑定
|
||||
public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) {
|
||||
super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host);
|
||||
// 让部件持有当前菜单实例,便于通信
|
||||
getHost().menu = this;
|
||||
// 初始同步部件上的开关状态到菜单(服务器端构造时保证一致)
|
||||
try {
|
||||
this.accelerateEnabled = getHost().getAccelerateEnabled();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// 当服务器数据同步到客户端时调用
|
||||
@Override
|
||||
public void onServerDataSync() {
|
||||
super.onServerDataSync();
|
||||
// 重新统计实体加速卡和能量卡数量
|
||||
this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
|
||||
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
|
||||
|
||||
// 计算当前面向方块的倍率(服务器端),并同步给客户端
|
||||
double mult = 1.0;
|
||||
try {
|
||||
BlockEntity target = getHost().getLevel().getBlockEntity(getHost().getBlockEntity().getBlockPos().relative(getHost().getSide()));
|
||||
if (target != null) {
|
||||
String blockId = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString();
|
||||
for (ConfigParsingUtils.MultiplierEntry me : ConfigParsingUtils.getCachedMultiplierEntries(List.of(ModConfig.INSTANCE.entityTickerMultipliers))) {
|
||||
if (me.pattern.matcher(blockId).matches()) {
|
||||
mult = Math.max(mult, me.multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
this.multiplier = mult;
|
||||
|
||||
// 检查目标是否在黑名单中,如果是则标记并将生效速度设为 0(服务器端计算)
|
||||
boolean blacklisted = false;
|
||||
try {
|
||||
BlockEntity target = getHost().getLevel().getBlockEntity(getHost().getBlockEntity().getBlockPos().relative(getHost().getSide()));
|
||||
if (target != null) {
|
||||
String blockId = ForgeRegistries.BLOCKS.getKey(target.getBlockState().getBlock()).toString();
|
||||
for (java.util.regex.Pattern p : ConfigParsingUtils.getCachedBlacklist(List.of(ModConfig.INSTANCE.entityTickerBlackList))) {
|
||||
if (p.matcher(blockId).matches()) {
|
||||
blacklisted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
this.targetBlacklisted = blacklisted;
|
||||
|
||||
// 计算生效速度:如果被黑名单则为 0,否则进行正常计算(使用工具类从菜单直接计算 product with cap,最多 8 张)
|
||||
if (this.targetBlacklisted) {
|
||||
this.effectiveSpeed = 0;
|
||||
} else {
|
||||
this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
|
||||
}
|
||||
|
||||
// 如果在客户端,刷新界面
|
||||
if (isClientSide()) {
|
||||
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
|
||||
screen.refreshGui();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当任意槽位发生变化时调用
|
||||
@Override
|
||||
public void onSlotChange(net.minecraft.world.inventory.Slot slot) {
|
||||
super.onSlotChange(slot);
|
||||
// 客户端重新统计卡数量并刷新界面
|
||||
if (isClientSide()) {
|
||||
this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
|
||||
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
|
||||
// 立即在客户端计算生效速度以便界面即时反馈(使用与服务端相同的工具方法,最多 8 张卡)
|
||||
this.effectiveSpeed = (int) PowerUtils.computeProductWithCapFromMenu(this, 8);
|
||||
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
|
||||
screen.refreshGui();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcastChanges(){
|
||||
// 遍历所有槽位,清理未启用但有物品显示的 OptionalFakeSlot
|
||||
for (Object o : this.slots) {
|
||||
if (o instanceof OptionalFakeSlot fs) {
|
||||
if (!fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) {
|
||||
fs.clearStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 调用标准的同步方法,通知监听者数据已更新
|
||||
this.standardDetectAndSendChanges();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,302 @@
|
|||
package com.extendedae_plus.ae.parts;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.config.PowerMultiplier;
|
||||
import appeng.api.networking.GridFlags;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.ticking.IGridTickable;
|
||||
import appeng.api.networking.ticking.TickRateModulation;
|
||||
import appeng.api.networking.ticking.TickingRequest;
|
||||
import appeng.api.parts.IPartCollisionHelper;
|
||||
import appeng.api.parts.IPartItem;
|
||||
import appeng.api.parts.IPartModel;
|
||||
import appeng.api.upgrades.IUpgradeableObject;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import appeng.items.parts.PartModels;
|
||||
import appeng.menu.MenuOpener;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import appeng.parts.PartModel;
|
||||
import appeng.parts.automation.UpgradeablePart;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.util.ConfigParsingUtils;
|
||||
import com.extendedae_plus.util.PowerUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* EntitySpeedTickerPart 是一个可升级的 AE2 部件<p>
|
||||
* 该部件可以加速目标方块实体的 tick 速率,消耗 AE 网络能量,并支持加速卡升级<p>
|
||||
* 功能受<a href="https://github.com/GilbertzRivi/crazyae2addons">Crazy AE2 Addons</a>启发
|
||||
*/
|
||||
public class EntitySpeedTickerPart extends UpgradeablePart implements IGridTickable, MenuProvider, IUpgradeableObject {
|
||||
// 当前打开的菜单实例(如果有)
|
||||
public EntitySpeedTickerMenu menu;
|
||||
|
||||
public static final ResourceLocation MODEL_BASE = new ResourceLocation(
|
||||
ExtendedAEPlus.MODID, "part/entity_speed_ticker_part");
|
||||
|
||||
@PartModels
|
||||
public static final PartModel MODELS_OFF;
|
||||
@PartModels
|
||||
public static final PartModel MODELS_ON;
|
||||
@PartModels
|
||||
public static final PartModel MODELS_HAS_CHANNEL;
|
||||
|
||||
static {
|
||||
MODELS_OFF = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off"));
|
||||
MODELS_ON = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on"));
|
||||
MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, new ResourceLocation(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数,初始化部件并设置网络节点属性
|
||||
* @param partItem 部件物品
|
||||
*/
|
||||
public EntitySpeedTickerPart(IPartItem<?> partItem) {
|
||||
super(partItem);
|
||||
// 设置网络节点属性:需要通道、空闲功耗为1,并注册为 IGridTickable 服务
|
||||
this.getMainNode()
|
||||
.setFlags(GridFlags.REQUIRE_CHANNEL)
|
||||
.setIdlePowerUsage(1)
|
||||
.addService(IGridTickable.class, this);
|
||||
}
|
||||
|
||||
// 控制是否启用加速(默认启用)
|
||||
private boolean accelerateEnabled = true;
|
||||
|
||||
|
||||
public boolean getAccelerateEnabled() {
|
||||
return this.accelerateEnabled;
|
||||
}
|
||||
|
||||
public void setAccelerateEnabled(boolean accelerateEnabled) {
|
||||
this.accelerateEnabled = accelerateEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前状态下的静态模型(用于渲染)
|
||||
* @return 当前状态的模型
|
||||
*/
|
||||
public IPartModel getStaticModels() {
|
||||
if (this.isActive() && this.isPowered()) {
|
||||
return MODELS_HAS_CHANNEL;
|
||||
} else if (this.isPowered()) {
|
||||
return MODELS_ON;
|
||||
} else {
|
||||
return MODELS_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当玩家激活部件(右键)时调用,打开自定义菜单
|
||||
* @param player 玩家
|
||||
* @param hand 手
|
||||
* @param pos 点击位置
|
||||
* @return 总是返回 true,表示激活成功
|
||||
*/
|
||||
@Override
|
||||
public boolean onPartActivate(Player player, InteractionHand hand, Vec3 pos) {
|
||||
// 仅在服务端打开菜单
|
||||
if (!player.getCommandSenderWorld().isClientSide()) {
|
||||
MenuOpener.open(ModMenuTypes.ENTITY_TICKER_MENU.get(), player, MenuLocators.forPart(this));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义部件的碰撞箱(用于物理碰撞和渲染)
|
||||
* @param bch 碰撞辅助器
|
||||
*/
|
||||
@Override
|
||||
public void getBoxes(IPartCollisionHelper bch) {
|
||||
bch.addBox(2, 2, 14, 14, 14, 16);
|
||||
bch.addBox(5, 5, 12, 11, 11, 14);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取定时请求,决定本部件多久 tick 一次
|
||||
* @param iGridNode 网络节点
|
||||
* @return TickingRequest 对象
|
||||
*/
|
||||
@Override
|
||||
public TickingRequest getTickingRequest(IGridNode iGridNode) {
|
||||
// 每 1 tick 执行一次
|
||||
return new TickingRequest(1, 1, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当升级卡数量发生变化时调用,通知菜单更新
|
||||
*/
|
||||
@Override
|
||||
public void upgradesChanged() {
|
||||
if (this.menu != null) {
|
||||
// 使用 AE2 风格:当升级发生变化时让菜单广播变化(槽/数据会被同步),客户端会基于槽内容重新计算并刷新界面
|
||||
this.menu.broadcastChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络定时回调,每次 tick 时调用
|
||||
* @param iGridNode 网络节点
|
||||
* @param ticksSinceLastCall 距离上次调用经过的 tick 数
|
||||
* @return TickRateModulation.IDLE 表示继续保持当前 tick 速率
|
||||
*/
|
||||
@Override
|
||||
public TickRateModulation tickingRequest(IGridNode iGridNode, int ticksSinceLastCall) {
|
||||
// 如果部件的加速开关被关闭,则不进行加速(提前返回)
|
||||
if (!this.getAccelerateEnabled()) {
|
||||
return TickRateModulation.IDLE;
|
||||
}
|
||||
|
||||
// 获取目标方块实体(本部件朝向的方块)
|
||||
BlockEntity target = getLevel().getBlockEntity(getBlockEntity().getBlockPos().relative(getSide()));
|
||||
// 仅在目标存在且部件处于激活状态时执行加速
|
||||
if (target != null && isActive()) {
|
||||
ticker(target);
|
||||
}
|
||||
return TickRateModulation.IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以指定速度对目标方块实体进行 tick 操作
|
||||
* @param blockEntity 需要被 tick 的方块实体
|
||||
* @param <T> 方块实体类型
|
||||
*/
|
||||
private <T extends BlockEntity> void ticker(@NotNull T blockEntity) {
|
||||
if (this.getGridNode() == null
|
||||
|| this.getMainNode() == null
|
||||
|| this.getMainNode().getGrid() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取方块实体的位置
|
||||
BlockPos pos = blockEntity.getBlockPos();
|
||||
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) return;
|
||||
|
||||
double extractedPower = getMainNode().getGrid().getEnergyService()
|
||||
.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG);
|
||||
if (extractedPower < requiredPower) return;
|
||||
|
||||
// 计算加速倍数:基于 2 的次方,并把 8 张映射到最大 1024x(2^10)
|
||||
// 已由 product 计算得到 speed;上面已在没有卡时提前返回
|
||||
|
||||
// 执行 tick 操作
|
||||
for (int i = 0; i < speed - 1; i++) {
|
||||
blockEntityTicker.tick(
|
||||
blockEntity.getLevel(),
|
||||
blockEntity.getBlockPos(),
|
||||
blockEntity.getBlockState(),
|
||||
blockEntity
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断部件是否有自定义名称
|
||||
* @return 是否有自定义名称
|
||||
*/
|
||||
@Override
|
||||
public boolean hasCustomName() {
|
||||
return super.hasCustomName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部件的显示名称
|
||||
* @return 显示名称
|
||||
*/
|
||||
@Override
|
||||
public @NotNull Component getDisplayName() {
|
||||
return super.getDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建自定义菜单(GUI)
|
||||
* @param containerId 容器ID
|
||||
* @param playerInventory 玩家背包
|
||||
* @param player 玩家
|
||||
* @return 菜单实例
|
||||
*/
|
||||
@Override
|
||||
public @Nullable AbstractContainerMenu createMenu(int containerId,
|
||||
@NotNull Inventory playerInventory,
|
||||
@NotNull Player player) {
|
||||
return new EntitySpeedTickerMenu(containerId, playerInventory, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用的升级卡槽数量
|
||||
* @return 升级卡槽数量
|
||||
*/
|
||||
@Override
|
||||
protected int getUpgradeSlots() {
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.extendedae_plus.ae.screen;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.client.gui.Icon;
|
||||
import appeng.client.gui.implementations.UpgradeableScreen;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.widgets.CommonButtons;
|
||||
import appeng.client.gui.widgets.SettingToggleButton;
|
||||
import appeng.util.Platform;
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleEntityTickerC2SPacket;
|
||||
import com.extendedae_plus.util.PowerUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends UpgradeableScreen<C> {
|
||||
private boolean eap$entitySpeedTickerEnabled = false;
|
||||
private SettingToggleButton<YesNo> eap$entitySpeedTickerToggle;
|
||||
|
||||
public EntitySpeedTickerScreen(
|
||||
EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super((C) menu, playerInventory, title, style);
|
||||
this.addToLeftToolbar(CommonButtons.togglePowerUnit());
|
||||
|
||||
try{
|
||||
this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
|
||||
}catch (Exception ignored){}
|
||||
|
||||
// 使用 SettingToggleButton<YesNo> 的外观(原版图标),但自定义悬停描述为“智能阻挡”
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
eap$entitySpeedTickerToggle = new SettingToggleButton<>(
|
||||
Settings.BLOCKING_MODE,
|
||||
this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleEntityTickerC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public List<Component> getTooltipMessage() {
|
||||
// 如果目标在黑名单中,直接显示已禁用的提示
|
||||
try {
|
||||
if (menu != null && menu.targetBlacklisted) {
|
||||
var title = Component.literal("实体加速");
|
||||
var stateLine = Component.literal("已禁用(目标在黑名单)");
|
||||
return List.of(title, stateLine);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
boolean enabled = eap$entitySpeedTickerEnabled;
|
||||
var title = Component.literal("实体加速");
|
||||
var stateLine = enabled
|
||||
? Component.literal("已启用: 将加速目标方块实体的tick")
|
||||
: Component.literal("已关闭: 不会对目标方块实体进行加速");
|
||||
return List.of(title, stateLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Icon getIcon() {
|
||||
try {
|
||||
if (menu != null && menu.targetBlacklisted) {
|
||||
// 黑名单时显示禁用图标
|
||||
return Icon.INVALID;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// 根据当前值显示不同图标(可按需替换 Icon 常量)
|
||||
if (this.getCurrentValue() == YesNo.YES) {
|
||||
return Icon.VALID;
|
||||
} else {
|
||||
return Icon.INVALID;
|
||||
}
|
||||
}
|
||||
};
|
||||
// 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
|
||||
eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO);
|
||||
|
||||
this.addToLeftToolbar(eap$entitySpeedTickerToggle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateBeforeRender() {
|
||||
super.updateBeforeRender();
|
||||
|
||||
if (this.eap$entitySpeedTickerToggle != null) {
|
||||
boolean desired = this.eap$entitySpeedTickerEnabled;
|
||||
if (this.menu != null) {
|
||||
desired = this.menu.getAccelerateEnabled();
|
||||
}
|
||||
|
||||
this.eap$entitySpeedTickerEnabled = desired;
|
||||
// 如果目标在黑名单中,禁用切换并强制显示为关闭
|
||||
if (this.menu != null && this.menu.targetBlacklisted) {
|
||||
this.eap$entitySpeedTickerToggle.set(YesNo.NO);
|
||||
this.eap$entitySpeedTickerToggle.active = false;
|
||||
} else {
|
||||
this.eap$entitySpeedTickerToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
this.eap$entitySpeedTickerToggle.active = true;
|
||||
}
|
||||
}
|
||||
textData();
|
||||
}
|
||||
|
||||
public void refreshGui() {
|
||||
textData();
|
||||
}
|
||||
|
||||
private void textData() {
|
||||
// 如果目标被黑名单禁止,则显示禁用状态并把数值显示为 0
|
||||
if (getMenu().targetBlacklisted) {
|
||||
setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable"));
|
||||
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0));
|
||||
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false)));
|
||||
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0)));
|
||||
setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0)));
|
||||
return;
|
||||
}
|
||||
|
||||
int energyCardCount = getMenu().energyCardCount;
|
||||
double multiplier = getMenu().multiplier;
|
||||
int effectiveSpeed = getMenu().effectiveSpeed;
|
||||
|
||||
double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount);
|
||||
double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount);
|
||||
|
||||
setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed));
|
||||
setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false)));
|
||||
setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio)));
|
||||
setTextContent("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier)));
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,6 @@ public final class ClientModelEvents {
|
|||
event.register(ExtendedAEPlus.id("block/crafting/64x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/256x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"));
|
||||
ClientProxy.init();
|
||||
ClientRegistrar.initBuiltInModels();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import appeng.client.render.crafting.CraftingCubeModel;
|
||||
import appeng.init.client.InitScreens;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.ae.screen.EntitySpeedTickerScreen;
|
||||
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
|
||||
import com.extendedae_plus.client.screen.GlobalProviderModesScreen;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||
import com.extendedae_plus.hooks.BuiltInModelHooks;
|
||||
import net.minecraftforge.client.ConfigScreenHandler;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
|
||||
/**
|
||||
* 客户端模型注册,将 formed 模型注册为内置模型。
|
||||
*/
|
||||
public final class ClientProxy {
|
||||
private ClientProxy() {}
|
||||
public final class ClientRegistrar {
|
||||
private ClientRegistrar() {}
|
||||
|
||||
private static boolean REGISTERED = false;
|
||||
|
||||
public static void init() {
|
||||
/**
|
||||
* 注册内置模型(formed 模型等)。可被 ModelEvent 或启动阶段直接调用。
|
||||
*/
|
||||
public static void initBuiltInModels() {
|
||||
if (REGISTERED) return;
|
||||
REGISTERED = true;
|
||||
// 注册 Item property,用于根据 ItemStack 的 NBT exponent 切换模型
|
||||
ItemProperties.register(ModItems.ENTITY_SPEED_CARD.get(), ExtendedAEPlus.id("mult"),
|
||||
(stack, world, entity, seed) -> (float) EntitySpeedCardItem.readMultiplier(stack));
|
||||
// 注册四种形成态模型为内置模型
|
||||
BuiltInModelHooks.addBuiltInModel(
|
||||
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
|
||||
|
|
@ -46,29 +55,31 @@ public final class ClientProxy {
|
|||
}
|
||||
|
||||
/**
|
||||
* 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。
|
||||
* 将菜单类型与对应的屏幕绑定。
|
||||
*/
|
||||
public static void onClientSetup(final FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
// 确保在首次资源加载前完成内置模型注册(REGISTERED 保护避免重复)
|
||||
init();
|
||||
// 仅在客户端设置阶段执行与 UI 相关的一次性绑定
|
||||
registerConfigScreen();
|
||||
// 菜单 -> 屏幕 绑定
|
||||
MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new);
|
||||
});
|
||||
public static void registerMenuScreens() {
|
||||
MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册由 AE2 InitScreens 所需的屏幕资源映射(用于内置 JSON 屏幕注册)
|
||||
*/
|
||||
public static void registerInitScreens() {
|
||||
InitScreens.register(ModMenuTypes.ENTITY_TICKER_MENU.get(),
|
||||
EntitySpeedTickerScreen<EntitySpeedTickerMenu>::new,
|
||||
"/screens/entity_speed_ticker.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅客户端:在 Mods 菜单注册配置界面入口。
|
||||
* 将对 Screen 的引用限制在客户端侧,避免服务端类加载。
|
||||
*/
|
||||
public static void registerConfigScreen() {
|
||||
// 将 ModConfigScreen 的引用放在此处,确保仅在 Dist.CLIENT 下解析该类
|
||||
ModLoadingContext.get().registerExtensionPoint(
|
||||
ConfigScreenHandler.ConfigScreenFactory.class,
|
||||
() -> new ConfigScreenHandler.ConfigScreenFactory(
|
||||
(mc, parent) -> new com.extendedae_plus.client.ModConfigScreen(parent))
|
||||
);
|
||||
}
|
||||
// public static void registerConfigScreen() {
|
||||
// // 将 ModConfigScreen 的引用放在此处,确保仅在 Dist.CLIENT 下解析该类
|
||||
// ModLoadingContext.get().registerExtensionPoint(
|
||||
// ConfigScreenHandler.ConfigScreenFactory.class,
|
||||
// () -> new ConfigScreenHandler.ConfigScreenFactory(
|
||||
// (mc, parent) -> new ModConfigScreen(parent))
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
|
@ -4,16 +4,16 @@ import appeng.api.stacks.GenericStack;
|
|||
import appeng.client.gui.me.common.MEStorageScreen;
|
||||
import appeng.integration.modules.jei.GenericEntryStackHelper;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor;
|
||||
import com.extendedae_plus.network.OpenCraftFromJeiC2SPacket;
|
||||
import com.extendedae_plus.network.PullFromJeiOrCraftC2SPacket;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ScreenEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
|
@ -98,13 +98,18 @@ public final class InputEvents {
|
|||
var screen = Minecraft.getInstance().screen;
|
||||
if (screen instanceof MEStorageScreen<?> me) {
|
||||
try {
|
||||
MEStorageScreenAccessor acc = (MEStorageScreenAccessor) (Object) me;
|
||||
MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me;
|
||||
acc.eap$getSearchField().setValue(name);
|
||||
acc.eap$setSearchText(name); // 同步到 Repo 并刷新
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}else if (screen instanceof GuiExPatternTerminal<?> gpt) {
|
||||
try {
|
||||
GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt;
|
||||
acc.getSearchOutField().setValue(name);
|
||||
event.setCanceled(true);
|
||||
}catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,172 +0,0 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.CycleButton;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class ModConfigScreen extends Screen {
|
||||
private final Screen parent;
|
||||
|
||||
// 输入控件
|
||||
private EditBox pageMultiplierBox;
|
||||
private EditBox wirelessMaxRangeBox;
|
||||
private CycleButton<Boolean> crossDimToggle;
|
||||
private CycleButton<Boolean> providerRoundRobinToggle;
|
||||
private EditBox smartScalingMaxMulBox;
|
||||
private EditBox smartScalingMinBenefitBox;
|
||||
private CycleButton<Boolean> showEncoderToggle;
|
||||
private CycleButton<Boolean> patternTerminalShowSlotsToggle;
|
||||
|
||||
public ModConfigScreen(Screen parent) {
|
||||
super(Component.translatable("screen.extendedae_plus.title"));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
int centerX = this.width / 2;
|
||||
int y = this.height / 6 + 24; // 起始高度,整体更上方
|
||||
int row = 0;
|
||||
int rowHeight = 26;
|
||||
int boxWidth = 150;
|
||||
// 左右两列:左侧标签起点,右侧输入控件起点
|
||||
int leftX = centerX - 170;
|
||||
int rightX = centerX + 20;
|
||||
|
||||
// pageMultiplier: Int 1-64
|
||||
pageMultiplierBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.pageMultiplier"));
|
||||
pageMultiplierBox.setValue(String.valueOf(ModConfigs.PAGE_MULTIPLIER.get()));
|
||||
pageMultiplierBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 1) >= 1 && parseIntOrDefault(s, 64) <= 64);
|
||||
this.addRenderableWidget(pageMultiplierBox);
|
||||
row++;
|
||||
|
||||
// wirelessMaxRange: Double 1-4096
|
||||
wirelessMaxRangeBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.wirelessMaxRange"));
|
||||
wirelessMaxRangeBox.setValue(String.valueOf(ModConfigs.WIRELESS_MAX_RANGE.get()));
|
||||
wirelessMaxRangeBox.setFilter(s -> s.isEmpty() || s.matches("\\d*(\\.\\d*)?"));
|
||||
this.addRenderableWidget(wirelessMaxRangeBox);
|
||||
row++;
|
||||
|
||||
// cross dim toggle
|
||||
crossDimToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get()));
|
||||
row++;
|
||||
|
||||
// provider round-robin toggle (smart doubling)
|
||||
providerRoundRobinToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get()));
|
||||
row++;
|
||||
|
||||
// smartScalingMaxMultiplier: Int 0-1048576 (0 means unlimited)
|
||||
smartScalingMaxMulBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.smartScalingMaxMultiplier"));
|
||||
smartScalingMaxMulBox.setValue(String.valueOf(ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get()));
|
||||
smartScalingMaxMulBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 0) >= 0 && parseIntOrDefault(s, 1048576) <= 1048576);
|
||||
this.addRenderableWidget(smartScalingMaxMulBox);
|
||||
row++;
|
||||
|
||||
// smartScalingMinBenefitFactor: Int 1-1024
|
||||
smartScalingMinBenefitBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.smartScalingMinBenefitFactor"));
|
||||
smartScalingMinBenefitBox.setValue(String.valueOf(ModConfigs.SMART_SCALING_MIN_BENEFIT_FACTOR.get()));
|
||||
smartScalingMinBenefitBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 1) >= 1 && parseIntOrDefault(s, 1024) <= 1024);
|
||||
this.addRenderableWidget(smartScalingMinBenefitBox);
|
||||
row++;
|
||||
|
||||
// show encoder pattern player toggle
|
||||
showEncoderToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.get()));
|
||||
row++;
|
||||
|
||||
// pattern terminal show slots default toggle
|
||||
patternTerminalShowSlotsToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT.get()));
|
||||
row++;
|
||||
|
||||
// 按钮:保存、返回
|
||||
int btnW = 100;
|
||||
int gap = 8;
|
||||
int buttonsY = y + row * rowHeight + 18;
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("gui.done"), b -> saveAndClose())
|
||||
.pos(centerX - btnW - gap/2, buttonsY)
|
||||
.size(btnW, 20)
|
||||
.build());
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("gui.cancel"), b -> onClose())
|
||||
.pos(centerX + gap/2, buttonsY)
|
||||
.size(btnW, 20)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void saveAndClose() {
|
||||
// 读取与校验
|
||||
int pageMul = clamp(parseIntOrDefault(pageMultiplierBox.getValue(), ModConfigs.PAGE_MULTIPLIER.get()), 1, 64);
|
||||
double maxRange = clamp(parseDoubleOrDefault(wirelessMaxRangeBox.getValue(), ModConfigs.WIRELESS_MAX_RANGE.get()), 1.0, 4096.0);
|
||||
boolean crossDim = crossDimToggle.getValue();
|
||||
boolean providerRoundRobin = providerRoundRobinToggle.getValue();
|
||||
int smartMaxMul = clamp(parseIntOrDefault(smartScalingMaxMulBox.getValue(), ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get()), 0, 1048576);
|
||||
int smartMinBenefit = clamp(parseIntOrDefault(smartScalingMinBenefitBox.getValue(), ModConfigs.SMART_SCALING_MIN_BENEFIT_FACTOR.get()), 1, 1024);
|
||||
boolean showEncoder = showEncoderToggle.getValue();
|
||||
boolean patternShowSlots = patternTerminalShowSlotsToggle.getValue();
|
||||
|
||||
// 应用到 Forge 配置值
|
||||
ModConfigs.PAGE_MULTIPLIER.set(pageMul);
|
||||
ModConfigs.WIRELESS_MAX_RANGE.set(maxRange);
|
||||
ModConfigs.WIRELESS_CROSS_DIM_ENABLE.set(crossDim);
|
||||
ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.set(providerRoundRobin);
|
||||
ModConfigs.SMART_SCALING_MAX_MULTIPLIER.set(smartMaxMul);
|
||||
ModConfigs.SMART_SCALING_MIN_BENEFIT_FACTOR.set(smartMinBenefit);
|
||||
ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.set(showEncoder);
|
||||
ModConfigs.PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT.set(patternShowSlots);
|
||||
|
||||
// Forge 会在合适的时机写回到配置文件;部分改动可能需要重启游戏或世界才完全生效
|
||||
onClose();
|
||||
}
|
||||
|
||||
// Helper to create a boolean on/off CycleButton which shows localized on/off text
|
||||
private CycleButton<Boolean> createToggle(int x, int y, int width, int height, boolean initial) {
|
||||
CycleButton<Boolean> btn = CycleButton.onOffBuilder(initial)
|
||||
.create(x, y, width, height, Component.empty(), (b, v) -> b.setMessage(Component.translatable(v ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off")));
|
||||
btn.setMessage(Component.translatable(initial ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off"));
|
||||
return btn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
Minecraft.getInstance().setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics g, int mouseX, int mouseY, float partialTick) {
|
||||
this.renderBackground(g);
|
||||
super.render(g, mouseX, mouseY, partialTick);
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int y = this.height / 6 + 24;
|
||||
int rowHeight = 26;
|
||||
int labelColor = 0xFFFFFF;
|
||||
int leftX = centerX - 170; // 标签左列位置
|
||||
|
||||
// 标题
|
||||
g.drawCenteredString(this.font, this.title, centerX, y - 28, 0xFFFFFF);
|
||||
|
||||
// 每行标签
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.pageMultiplier_with_range"), leftX, y + 0 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessMaxRange_with_range"), leftX, y + 1 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessCrossDimEnable"), leftX, y + 2 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.providerRoundRobinEnable"), leftX, y + 3 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.smartScalingMaxMultiplier_with_range"), leftX, y + 4 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.smartScalingMinBenefitFactor"), leftX, y + 5 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.showEncoderPatternPlayer"), leftX, y + 6 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.patternTerminalShowSlotsDefault"), leftX, y + 7 * rowHeight + 6, labelColor, false);
|
||||
}
|
||||
|
||||
private static int parseIntOrDefault(String s, int def) {
|
||||
try { return Integer.parseInt(s); } catch (Exception e) { return def; }
|
||||
}
|
||||
|
||||
private static double parseDoubleOrDefault(String s, double def) {
|
||||
try { return Double.parseDouble(s); } catch (Exception e) { return def; }
|
||||
}
|
||||
|
||||
private static int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }
|
||||
private static double clamp(double v, double min, double max) { return Math.max(min, Math.min(max, v)); }
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.extendedae_plus.client.screen;
|
||||
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import com.extendedae_plus.network.GlobalToggleProviderModesC2SPacket;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.extendedae_plus.client.ui;
|
||||
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
|
|
|
|||
128
src/main/java/com/extendedae_plus/config/ModConfig.java
Normal file
128
src/main/java/com/extendedae_plus/config/ModConfig.java
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package com.extendedae_plus.config;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import dev.toma.configuration.Configuration;
|
||||
import dev.toma.configuration.config.Config;
|
||||
import dev.toma.configuration.config.Configurable;
|
||||
import dev.toma.configuration.config.format.ConfigFormats;
|
||||
|
||||
|
||||
@Config(id = ExtendedAEPlus.MODID)
|
||||
public final class ModConfig {
|
||||
|
||||
public static ModConfig INSTANCE;
|
||||
private static final Object lock = new Object();
|
||||
|
||||
|
||||
public static void init() {
|
||||
synchronized (lock) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = Configuration.registerConfig(ModConfig.class, ConfigFormats.yaml()).getConfigInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"扩展样板供应器总槽位容量的倍率。",
|
||||
"基础为36,每页仍显示36格,倍率会增加总页数/总容量。",
|
||||
"建议范围 1-16"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
@Configurable.Range(min = 1, max = 64)
|
||||
public int pageMultiplier = 1;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"无线收发器最大连接距离(单位:方块)",
|
||||
"从端与主端的直线距离需小于等于该值才会建立连接"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
@Configurable.DecimalRange(min = 1, max = 4096)
|
||||
public double wirelessMaxRange = 256.0;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"是否允许无线收发器跨维度建立连接",
|
||||
"开启后,从端可连接到不同维度的主端(忽略距离限制)"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
public boolean wirelessCrossDimEnable = true;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"智能倍增时是否对样板供应器轮询分配",
|
||||
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
|
||||
"注意:所有相关供应器需开启智能倍增,否则可能失效"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
public boolean providerRoundRobinEnable = true;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"智能倍增的最大倍数(0 表示不限制)",
|
||||
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
@Configurable.Range(min = 0, max = Integer.MAX_VALUE)
|
||||
public int smartScalingMaxMultiplier = 0;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"智能倍增最小收益因子(默认 4)",
|
||||
"当目标请求量小于 perOperationTarget * 此因子 时,智能倍增将不被启用以避免无意义包装"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
@Configurable.Range(min = 1, max = 1024)
|
||||
public int smartScalingMinBenefitFactor = 4;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"是否显示样板编码玩家",
|
||||
"开启后将在样板 HoverText 上添加样板的编码玩家"
|
||||
})
|
||||
public boolean showEncoderPatternPlayer = true;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"样板终端默认是否显示槽位",
|
||||
"影响进入界面时SlotsRow的默认可见性,仅影响客户端显示"
|
||||
})
|
||||
public boolean patternTerminalShowSlotsDefault = true;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"实体加速器能量消耗基础值"
|
||||
})
|
||||
@Configurable.Range(min = 0, max = Integer.MAX_VALUE)
|
||||
@Configurable.Synchronized
|
||||
public int entityTickerCost = 512;
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"实体加速器黑名单:匹配的方块将不会被加速。支持通配符/正则(例如:minecraft:*)",
|
||||
"格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
public String[] entityTickerBlackList = {
|
||||
|
||||
};
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"额外消耗倍率配置:为某些方块设置额外能量倍率,格式 'modid:blockid multiplier',例如 'minecraft:chest 2x'",
|
||||
"支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
public String[] entityTickerMultipliers = {
|
||||
|
||||
};
|
||||
|
||||
@Configurable
|
||||
@Configurable.Comment(value = {
|
||||
"值越大将减少AE构建合成计划过程中的 wait/notify 次数,提升吞吐但会降低调度响应性"
|
||||
})
|
||||
@Configurable.Synchronized
|
||||
@Configurable.Range(min = 100, max = Integer.MAX_VALUE)
|
||||
public int craftingPauseThreshold = 100000;
|
||||
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
package com.extendedae_plus.config;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
|
||||
public final class ModConfigs {
|
||||
public static final ForgeConfigSpec COMMON_SPEC;
|
||||
public static final ForgeConfigSpec.IntValue PAGE_MULTIPLIER;
|
||||
public static final ForgeConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
|
||||
public static final ForgeConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
|
||||
public static final ForgeConfigSpec.BooleanValue SHOW_ENCOD_PATTERN_PLAYER;
|
||||
public static final ForgeConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
|
||||
public static final ForgeConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
|
||||
public static final ForgeConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
|
||||
public static final ForgeConfigSpec.IntValue SMART_SCALING_MIN_BENEFIT_FACTOR;
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
builder.push("extendedae_plus");
|
||||
PAGE_MULTIPLIER = builder
|
||||
.comment(
|
||||
"扩展样板供应器总槽位容量的倍率。",
|
||||
"基础为36,每页仍显示36格,倍率会增加总页数/总容量。",
|
||||
"建议范围 1-16")
|
||||
.defineInRange("pageMultiplier", 1, 1, 64);
|
||||
|
||||
// 无线收发器:最大连接距离(单位:方块)。
|
||||
// 一对多从端连接主端时,将以该值作为范围限制。
|
||||
WIRELESS_MAX_RANGE = builder
|
||||
.comment(
|
||||
"无线收发器最大连接距离(单位:方块)",
|
||||
"从端与主端的直线距离需小于等于该值才会建立连接。")
|
||||
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
|
||||
|
||||
// 是否允许跨维度连接(忽略维度差异进行频道传输)。
|
||||
WIRELESS_CROSS_DIM_ENABLE = builder
|
||||
.comment(
|
||||
"是否允许无线收发器跨维度建立连接",
|
||||
"开启后,从端可连接到不同维度的主端(忽略距离限制)")
|
||||
.define("wirelessCrossDimEnable", true);
|
||||
|
||||
SHOW_ENCOD_PATTERN_PLAYER = builder
|
||||
.comment(
|
||||
"是否显示样板编码玩家",
|
||||
"开启后将在样板 HoverText 上添加样板的编码玩家"
|
||||
)
|
||||
.define("showEncoderPatternPlayer", true);
|
||||
|
||||
// 智能倍增后,是否在样板供应器间轮询分配请求量(开启:按 provider 均分;关闭:不拆分)
|
||||
PROVIDER_ROUND_ROBIN_ENABLE = builder
|
||||
.comment(
|
||||
"智能倍增时是否对样板供应器轮询分配",
|
||||
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
|
||||
"注意:所有相关供应器需开启智能倍增,否则可能失效",
|
||||
"默认: true")
|
||||
.define("providerRoundRobinEnable", true);
|
||||
|
||||
// 智能倍增的最大倍数(以单次样板产出为单位)。
|
||||
// 0 表示不限制;>0 表示最大倍增倍数上限,例如 64 表示最多放大到 64 倍。
|
||||
SMART_SCALING_MAX_MULTIPLIER = builder
|
||||
.comment(
|
||||
"智能倍增的最大倍数(0 表示不限制)",
|
||||
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模")
|
||||
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
|
||||
|
||||
// 智能倍增最小收益因子:若 requestedAmount < perOperationTarget * 此因子,则不启用缩放(默认 4)
|
||||
SMART_SCALING_MIN_BENEFIT_FACTOR = builder
|
||||
.comment(
|
||||
"智能倍增最小收益因子(默认 4)",
|
||||
"当目标请求量小于 perOperationTarget * 此因子 时,智能倍增将不被启用以避免无意义包装")
|
||||
.defineInRange("smartScalingMinBenefitFactor", 4, 1, 1024);
|
||||
|
||||
// 模式访问终端(ExtendedAE 图样终端)默认是否显示槽位渲染(SlotsRow)。
|
||||
// true: 默认显示(可通过界面按钮临时隐藏);false: 默认隐藏(可通过按钮显示)
|
||||
PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT = builder
|
||||
.comment(
|
||||
"样板终端默认是否显示槽位",
|
||||
"影响进入界面时SlotsRow的默认可见性,仅影响客户端显示"
|
||||
)
|
||||
.define("patternTerminalShowSlotsDefault", true);
|
||||
builder.pop();
|
||||
COMMON_SPEC = builder.build();
|
||||
}
|
||||
|
||||
private ModConfigs() {}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.extendedae_plus.content.wireless;
|
||||
|
||||
import appeng.api.networking.*;
|
||||
import appeng.api.util.AECableType;
|
||||
import appeng.blockentity.AEBaseBlockEntity;
|
||||
import com.extendedae_plus.init.ModBlockEntities;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||
|
|
@ -11,11 +13,11 @@ import net.minecraft.core.Direction;
|
|||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 无线收发器方块实体(骨架):
|
||||
|
|
@ -24,7 +26,7 @@ import java.util.EnumSet;
|
|||
* - 集成 AE2 节点;
|
||||
* - 集成无线主/从逻辑。
|
||||
*/
|
||||
public class WirelessTransceiverBlockEntity extends BlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
|
||||
public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
|
||||
|
||||
private IManagedGridNode managedNode;
|
||||
|
||||
|
|
@ -38,7 +40,8 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
|||
public WirelessTransceiverBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.WIRELESS_TRANSCEIVER_BE.get(), pos, state);
|
||||
// 创建 AE2 管理节点
|
||||
this.managedNode = GridHelper.createManagedNode(this, NodeListener.INSTANCE);
|
||||
this.managedNode = GridHelper.createManagedNode(this, NodeListener.INSTANCE)
|
||||
.setFlags(GridFlags.DENSE_CAPACITY);
|
||||
this.managedNode.setIdlePowerUsage(1.0); // 可按需调整基础待机功耗
|
||||
this.managedNode.setTagName("wireless_node");
|
||||
this.managedNode.setInWorldNode(true);
|
||||
|
|
@ -50,6 +53,20 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
|||
this.slaveLink = new WirelessSlaveLink(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public appeng.api.util.AECableType getCableConnectionType(Direction dir) {
|
||||
// 根据相邻方块的实际连接类型渲染(优先采用相邻主机返回的类型),回退为 GLASS。
|
||||
if (this.level == null) return AECableType.GLASS;
|
||||
var adjacentPos = this.worldPosition.relative(dir);
|
||||
if (!Objects.requireNonNull(this.getLevel()).hasChunkAt(adjacentPos)) return AECableType.GLASS;
|
||||
var adjacentHost = GridHelper.getNodeHost(this.getLevel(), adjacentPos);
|
||||
if (adjacentHost != null) {
|
||||
var t = adjacentHost.getCableConnectionType(dir.getOpposite());
|
||||
if (t != null) return t;
|
||||
}
|
||||
return AECableType.GLASS;
|
||||
}
|
||||
|
||||
/* ===================== IInWorldGridNodeHost ===================== */
|
||||
@Override
|
||||
public @Nullable IGridNode getGridNode(Direction dir) {
|
||||
|
|
@ -170,7 +187,7 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
|||
|
||||
/* ===================== NBT ===================== */
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
public void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
tag.putLong("frequency", frequency);
|
||||
tag.putBoolean("master", masterMode);
|
||||
|
|
@ -181,15 +198,15 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
|||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
public void loadTag(CompoundTag tag) {
|
||||
super.loadTag(tag);
|
||||
this.frequency = tag.getLong("frequency");
|
||||
this.masterMode = tag.getBoolean("master");
|
||||
this.locked = tag.getBoolean("locked");
|
||||
|
||||
if (managedNode != null) {
|
||||
managedNode.loadFromNBT(tag);
|
||||
}
|
||||
// 应用到链接器
|
||||
if (masterMode) {
|
||||
masterLink.setFrequency(frequency);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ public final class ModCreativeTabs {
|
|||
output.accept(ModItems.ACCELERATOR_64x.get());
|
||||
output.accept(ModItems.ACCELERATOR_256x.get());
|
||||
output.accept(ModItems.ACCELERATOR_1024x.get());
|
||||
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
|
||||
|
||||
// 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
|
||||
output.accept(ModItems.createEntitySpeedCardStack(2));
|
||||
output.accept(ModItems.createEntitySpeedCardStack(4));
|
||||
output.accept(ModItems.createEntitySpeedCardStack(8));
|
||||
output.accept(ModItems.createEntitySpeedCardStack(16));
|
||||
|
||||
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
package com.extendedae_plus.init;
|
||||
|
||||
import appeng.api.parts.IPart;
|
||||
import appeng.api.parts.PartModels;
|
||||
import appeng.items.parts.PartModelsHelper;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||
import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem;
|
||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
|
|
@ -47,4 +53,40 @@ public final class ModItems {
|
|||
"1024x_crafting_accelerator",
|
||||
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<EntitySpeedTickerPartItem> ENTITY_TICKER_PART_ITEM = ITEMS.register(
|
||||
"entity_speed_ticker",
|
||||
() -> new EntitySpeedTickerPartItem(new Item.Properties())
|
||||
);
|
||||
|
||||
// AE Upgrade Cards: 实体加速卡(四个等级:x2,x4,x8,x16)
|
||||
// 单一实体加速卡 Item(不同等级由 ItemStack.nbt 存储)
|
||||
public static final RegistryObject<EntitySpeedCardItem> ENTITY_SPEED_CARD = ITEMS.register(
|
||||
"entity_speed_card",
|
||||
() -> new EntitySpeedCardItem(new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
|
||||
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* 为 PartItem 注册 AE2 部件模型。
|
||||
* 在客户端进行模型/几何体注册时调用。
|
||||
*/
|
||||
public static void registerPartModels() {
|
||||
PartModels.registerModels(
|
||||
PartModelsHelper.createModels(
|
||||
ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
|
||||
*/
|
||||
public static net.minecraft.world.item.ItemStack createEntitySpeedCardStack(int multiplier) {
|
||||
return EntitySpeedCardItem.withMultiplier(multiplier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.extendedae_plus.init;
|
||||
|
||||
import appeng.menu.implementations.MenuTypeBuilder;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraftforge.common.extensions.IForgeMenuType;
|
||||
|
|
@ -9,12 +12,19 @@ import net.minecraftforge.registries.ForgeRegistries;
|
|||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
public final class ModMenuTypes {
|
||||
private ModMenuTypes() {}
|
||||
|
||||
public static final DeferredRegister<MenuType<?>> MENUS =
|
||||
DeferredRegister.create(ForgeRegistries.MENU_TYPES, ExtendedAEPlus.MODID);
|
||||
|
||||
private ModMenuTypes() {
|
||||
}
|
||||
|
||||
public static final RegistryObject<MenuType<NetworkPatternControllerMenu>> NETWORK_PATTERN_CONTROLLER =
|
||||
MENUS.register("network_pattern_controller",
|
||||
() -> IForgeMenuType.create(NetworkPatternControllerMenu::new));
|
||||
|
||||
public static final RegistryObject<MenuType<EntitySpeedTickerMenu>> ENTITY_TICKER_MENU =
|
||||
MENUS.register("entity_speed_ticker",
|
||||
() -> MenuTypeBuilder
|
||||
.create(EntitySpeedTickerMenu::new, EntitySpeedTickerPart.class)
|
||||
.build("entity_speed_ticker"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.extendedae_plus.network;
|
||||
package com.extendedae_plus.init;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.network.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
|
|
@ -90,6 +91,18 @@ public class ModNetwork {
|
|||
.consumerNetworkThread(GlobalToggleProviderModesC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(ToggleEntityTickerC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(ToggleEntityTickerC2SPacket::encode)
|
||||
.decoder(ToggleEntityTickerC2SPacket::decode)
|
||||
.consumerNetworkThread(ToggleEntityTickerC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(InterfaceAdjustConfigAmountC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(InterfaceAdjustConfigAmountC2SPacket::encode)
|
||||
.decoder(InterfaceAdjustConfigAmountC2SPacket::decode)
|
||||
.consumerNetworkThread(InterfaceAdjustConfigAmountC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(AdvancedBlockingSyncS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(AdvancedBlockingSyncS2CPacket::encode)
|
||||
.decoder(AdvancedBlockingSyncS2CPacket::decode)
|
||||
17
src/main/java/com/extendedae_plus/init/UpgradeCards.java
Normal file
17
src/main/java/com/extendedae_plus/init/UpgradeCards.java
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package com.extendedae_plus.init;
|
||||
|
||||
import appeng.api.upgrades.Upgrades;
|
||||
import appeng.core.definitions.AEItems;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
|
||||
|
||||
public class UpgradeCards {
|
||||
public UpgradeCards(final FMLCommonSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
// 现有:把 Entity Ticker 的部件注册为处理 SPEED/ENERGY 卡的宿主
|
||||
Upgrades.add(AEItems.ENERGY_CARD, ModItems.ENTITY_TICKER_PART_ITEM.get(), 8, "group.entity_ticker.name");
|
||||
// 使用单一的 UpgradeCard Item 作为注册键,总共允许安装 4 张(不同等级由 ItemStack NBT 区分)
|
||||
Upgrades.add(ModItems.ENTITY_SPEED_CARD.get(), ModItems.ENTITY_TICKER_PART_ITEM.get(), 4, "group.entity_ticker.name");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,12 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr
|
|||
if (data.contains("masterMode") && !data.getBoolean("masterMode") && data.contains("masterPos")) {
|
||||
BlockPos pos = BlockPos.of(data.getLong("masterPos"));
|
||||
String dim = data.contains("masterDim") ? data.getString("masterDim") : "";
|
||||
tooltip.add(Component.literal("主节点位置: (" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
|
||||
String customName = data.contains("customName") ? data.getString("customName") : null;
|
||||
if (customName != null) {
|
||||
tooltip.add(Component.literal("主节点: " + customName + "(" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
|
||||
} else {
|
||||
tooltip.add(Component.literal("主节点位置: (" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
|
||||
}
|
||||
if (!dim.isEmpty()) {
|
||||
tooltip.add(Component.literal("维度: " + dim));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
|
|||
long freq = blockEntity.getFrequency();
|
||||
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
|
||||
if (master != null && !master.isEndpointRemoved()) {
|
||||
if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) {
|
||||
data.putString("customName", masterBlockEntity.getCustomName().getString());
|
||||
}
|
||||
BlockPos pos = master.getBlockPos();
|
||||
if (pos != null) {
|
||||
data.putLong("masterPos", pos.asLong());
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.extendedae_plus.integration.jei;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||
import mezz.jei.api.IModPlugin;
|
||||
import mezz.jei.api.JeiPlugin;
|
||||
import mezz.jei.api.registration.ISubtypeRegistration;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
|
@ -19,4 +21,13 @@ public class ExtendedAEJeiPlugin implements IModPlugin {
|
|||
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
|
||||
JeiRuntimeProxy.setRuntime(jeiRuntime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerItemSubtypes(ISubtypeRegistration registration) {
|
||||
// Register NBT-based subtype interpreter so JEI treats different multipliers as distinct items
|
||||
registration.registerSubtypeInterpreter(
|
||||
com.extendedae_plus.init.ModItems.ENTITY_SPEED_CARD.get(),
|
||||
(stack, context) -> String.valueOf(EntitySpeedCardItem.readMultiplier(stack))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,23 @@ package com.extendedae_plus.integration.jei;
|
|||
|
||||
import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.forge.ForgeTypes;
|
||||
import mezz.jei.api.ingredients.IIngredientType;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import mezz.jei.api.runtime.IBookmarkOverlay;
|
||||
import mezz.jei.api.runtime.IIngredientListOverlay;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import mezz.jei.gui.bookmarks.BookmarkList;
|
||||
import mezz.jei.gui.bookmarks.IngredientBookmark;
|
||||
import mezz.jei.gui.overlay.elements.IElement;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
|
@ -17,10 +26,12 @@ import java.util.Optional;
|
|||
/**
|
||||
* 线程安全地缓存并访问 JEI Runtime。
|
||||
*/
|
||||
@Pseudo
|
||||
public final class JeiRuntimeProxy {
|
||||
private static volatile IJeiRuntime RUNTIME;
|
||||
|
||||
private JeiRuntimeProxy() {}
|
||||
private JeiRuntimeProxy() {
|
||||
}
|
||||
|
||||
static void setRuntime(IJeiRuntime runtime) {
|
||||
RUNTIME = runtime;
|
||||
|
|
@ -132,4 +143,128 @@ public final class JeiRuntimeProxy {
|
|||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static void addBookmark(ItemStack stack) {
|
||||
IJeiRuntime rt = RUNTIME;
|
||||
if (rt == null) return;
|
||||
|
||||
IBookmarkOverlay overlay = rt.getBookmarkOverlay();
|
||||
if (overlay instanceof BookmarkOverlayAccessor accessor) {
|
||||
BookmarkList list = accessor.eap$getBookmarkList();
|
||||
Optional<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 = RUNTIME;
|
||||
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 = RUNTIME;
|
||||
if (rt == null) return;
|
||||
|
||||
IBookmarkOverlay overlay = rt.getBookmarkOverlay();
|
||||
if (overlay instanceof BookmarkOverlayAccessor accessor) {
|
||||
BookmarkList list = accessor.eap$getBookmarkList();
|
||||
try {
|
||||
if (chemicalStack == null) return;
|
||||
|
||||
// Determine Mekanism JEI ingredient type constant by runtime class name
|
||||
String clsName = chemicalStack.getClass().getName();
|
||||
String mekanismJeiClass = "mekanism.client.jei.MekanismJEI";
|
||||
Class<?> jeiCls = Class.forName(mekanismJeiClass);
|
||||
Field typeField = getField(clsName, jeiCls);
|
||||
|
||||
if (typeField == null) return;
|
||||
Object typeConst = typeField.get(null);
|
||||
|
||||
// Use ingredient manager reflectively to create a typed ingredient
|
||||
Object ingredientManager = rt.getIngredientManager();
|
||||
Method createTypedIngredient = ingredientManager.getClass().getMethod("createTypedIngredient", IIngredientType.class, Object.class);
|
||||
Object opt = createTypedIngredient.invoke(ingredientManager, typeConst, chemicalStack);
|
||||
if (!(opt instanceof Optional<?> typedOpt)) return;
|
||||
if (typedOpt.isPresent()) {
|
||||
Object typed = typedOpt.get();
|
||||
// Find a compatible static create(...) method on IngredientBookmark where
|
||||
// the second parameter is assignable from the actual ingredientManager instance.
|
||||
Method createMethod = null;
|
||||
for (Method m : IngredientBookmark.class.getMethods()) {
|
||||
if (!m.getName().equals("create")) continue;
|
||||
Class<?>[] params = m.getParameterTypes();
|
||||
if (params.length != 2) continue;
|
||||
// first param should accept the typed ingredient
|
||||
boolean firstOk = params[0].isAssignableFrom(typed.getClass()) || params[0].isAssignableFrom(ITypedIngredient.class);
|
||||
boolean secondOk = params[1].isAssignableFrom(ingredientManager.getClass());
|
||||
if (firstOk && secondOk) {
|
||||
createMethod = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (createMethod != null) {
|
||||
Object bookmark = createMethod.invoke(null, typed, ingredientManager);
|
||||
if (bookmark != null) {
|
||||
list.add((IngredientBookmark) bookmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = RUNTIME;
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.extendedae_plus.mixin;
|
||||
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.PickFromWirelessC2SPacket;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.accessor;
|
|||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
|
|
@ -11,4 +12,5 @@ public interface AbstractContainerScreenAccessor<T extends AbstractContainerMenu
|
|||
@Accessor("topPos") int eap$getTopPos();
|
||||
@Accessor("imageWidth") int eap$getImageWidth();
|
||||
@Accessor("imageHeight") int eap$getImageHeight();
|
||||
@Accessor("hoveredSlot") Slot eap$getHoveredSlot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.extendedae_plus.mixin.advancedae;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**适配
|
||||
* Redirect PatternProviderLogic.pushPattern 中对 List.contains 的调用,
|
||||
* 在遇到缩放样板时回退匹配到原始样板实例。
|
||||
*/
|
||||
@Mixin(value = AdvPatternProviderLogic.class, remap = false)
|
||||
public class AdvPatternProviderLogicContainsRedirectMixin {
|
||||
|
||||
@Redirect(method = "pushPattern",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/util/List;contains(Ljava/lang/Object;)Z")
|
||||
)
|
||||
private boolean eap$patternsContains(List<?> list, Object o) {
|
||||
try {
|
||||
if (o instanceof ScaledProcessingPattern scaled) {
|
||||
IPatternDetails base = scaled.getOriginal();
|
||||
if (base != null && list.indexOf(base) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 使用 indexOf 避免再次触发对 List.contains 的 redirect(防止递归)
|
||||
return list.indexOf(o) != -1;
|
||||
} catch (Throwable t) {
|
||||
return list.indexOf(o) != -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.extendedae_plus.mixin.advancedae.accessor;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(value = AdvPatternProviderLogic.class, remap = false)
|
||||
public interface AdvPatternProviderLogicPatternsAccessor {
|
||||
@Accessor("patterns")
|
||||
List<IPatternDetails> eap$patterns();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.extendedae_plus.mixin.advancedae.accessor;
|
||||
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(AdvPatternProviderMenu.class)
|
||||
public interface AdvPatternProviderMenuAdvancedAccessor {
|
||||
@Accessor(value = "logic", remap = false)
|
||||
AdvPatternProviderLogic eap$logic();
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.extendedae_plus.mixin.advancedae.client.gui;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.widgets.SettingToggleButton;
|
||||
import com.extendedae_plus.api.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.pedroksl.advanced_ae.client.gui.AdvPatternProviderScreen;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
/**
|
||||
* 为高级ae样板供应器界面添加“高级阻挡模式”按钮。
|
||||
* - 位于左侧工具栏
|
||||
* - 点击仅发送 C2S 切换请求;状态由 AE2 @GuiSync 回传决定
|
||||
*/
|
||||
@Mixin(AdvPatternProviderScreen.class)
|
||||
public abstract class AdvPatternProviderScreenMixin extends AEBaseScreen<AdvPatternProviderMenu> {
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$AdvancedBlockingEnabled = false;
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$SmartDoublingEnabled = false;
|
||||
|
||||
public AdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super((AdvPatternProviderMenu) menu, playerInventory, title, style);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||
// 使用 @GuiSync 初始化
|
||||
try {
|
||||
if (menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing advanced sync", t);
|
||||
}
|
||||
|
||||
// 使用 SettingToggleButton<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) {
|
||||
LOGGER.error("Error initializing smart doubling sync", t);
|
||||
}
|
||||
|
||||
this.eap$SmartDoublingToggle = new SettingToggleButton<>(
|
||||
Settings.BLOCKING_MODE,
|
||||
this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<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)同步布尔值,保持按钮状态一致
|
||||
@Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
|
||||
private void eap$updateAdvancedBlocking(CallbackInfo ci) {
|
||||
if (this.eap$AdvancedBlockingToggle != null) {
|
||||
boolean desired = this.eap$AdvancedBlockingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
||||
if (this.eap$SmartDoublingToggle != null) {
|
||||
boolean desired2 = this.eap$SmartDoublingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
|
||||
desired2 = sync2.eap$getSmartDoublingSynced();
|
||||
}
|
||||
this.eap$SmartDoublingEnabled = desired2;
|
||||
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
||||
if ((Object) this instanceof GuiExPatternProvider) {
|
||||
try {
|
||||
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.extendedae_plus.mixin.advancedae.client.gui;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.widgets.SettingToggleButton;
|
||||
import com.extendedae_plus.api.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.pedroksl.advanced_ae.client.gui.SmallAdvPatternProviderScreen;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.SmallAdvPatternProviderMenu;
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
/**
|
||||
* 为高级ae样板供应器界面添加“高级阻挡模式”按钮。
|
||||
* - 位于左侧工具栏
|
||||
* - 点击仅发送 C2S 切换请求;状态由 AE2 @GuiSync 回传决定
|
||||
*/
|
||||
@Mixin(SmallAdvPatternProviderScreen.class)
|
||||
public abstract class SmallAdvPatternProviderScreenMixin extends AEBaseScreen<SmallAdvPatternProviderMenu> {
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$AdvancedBlockingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$AdvancedBlockingEnabled = false;
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$SmartDoublingEnabled = false;
|
||||
|
||||
public SmallAdvPatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super((SmallAdvPatternProviderMenu) menu, playerInventory, title, style);
|
||||
}
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||
// 使用 @GuiSync 初始化
|
||||
try {
|
||||
if (menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
this.eap$AdvancedBlockingEnabled = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing advanced sync", t);
|
||||
}
|
||||
|
||||
// 使用 SettingToggleButton<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) {
|
||||
LOGGER.error("Error initializing smart doubling sync", t);
|
||||
}
|
||||
|
||||
this.eap$SmartDoublingToggle = new SettingToggleButton<>(
|
||||
Settings.BLOCKING_MODE,
|
||||
this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<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)同步布尔值,保持按钮状态一致
|
||||
@Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
|
||||
private void eap$updateAdvancedBlocking(CallbackInfo ci) {
|
||||
if (this.eap$AdvancedBlockingToggle != null) {
|
||||
boolean desired = this.eap$AdvancedBlockingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
||||
if (this.eap$SmartDoublingToggle != null) {
|
||||
boolean desired2 = this.eap$SmartDoublingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
|
||||
desired2 = sync2.eap$getSmartDoublingSynced();
|
||||
}
|
||||
this.eap$SmartDoublingEnabled = desired2;
|
||||
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
||||
if ((Object) this instanceof GuiExPatternProvider) {
|
||||
try {
|
||||
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.debug("[EAP] updateButtonsLayout skipped: {}", t.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.extendedae_plus.mixin.advancedae.helpers;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.crafting.IPatternDetails.IInput;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.helpers.patternprovider.PatternProviderTarget;
|
||||
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Mixin(value = AdvPatternProviderLogic.class, remap = false)
|
||||
public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
|
||||
@Unique
|
||||
private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking";
|
||||
|
||||
@Unique
|
||||
private boolean eap$advancedBlocking = false;
|
||||
|
||||
@Override
|
||||
public boolean eap$getAdvancedBlocking() {
|
||||
return eap$advancedBlocking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setAdvancedBlocking(boolean value) {
|
||||
this.eap$advancedBlocking = value;
|
||||
}
|
||||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeAdvancedToNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readAdvancedFromNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
if (tag.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = tag.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
// 在 pushPattern 中,重定向对 adapter.containsPatternInput(...) 的调用
|
||||
@Redirect(method = "pushPattern", at = @At(value = "INVOKE", target = "Lappeng/helpers/patternprovider/PatternProviderTarget;containsPatternInput(Ljava/util/Set;)Z"))
|
||||
private boolean eap$redirectBlockingContains(PatternProviderTarget adapter,
|
||||
java.util.Set<AEKey> patternInputs,
|
||||
IPatternDetails patternDetails,
|
||||
appeng.api.stacks.KeyCounter[] inputHolder) {
|
||||
// 原版是否打开阻挡
|
||||
boolean vanillaBlocking = ((AdvPatternProviderLogic)(Object)this).isBlocking();
|
||||
if (!vanillaBlocking) {
|
||||
return adapter.containsPatternInput(patternInputs);
|
||||
}
|
||||
|
||||
// 仅当高级阻挡启用时启用“匹配则不阻挡”
|
||||
if (this.eap$advancedBlocking) {
|
||||
if (eap$targetFullyMatchesPatternInputs(adapter, patternDetails)) {
|
||||
// 返回 false 表示“不包含阻挡关键物”,从而不触发 continue,允许发配
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 否则使用原判定
|
||||
return adapter.containsPatternInput(patternInputs);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean eap$targetFullyMatchesPatternInputs(PatternProviderTarget adapter, IPatternDetails patternDetails) {
|
||||
for (IInput in : patternDetails.getInputs()) {
|
||||
boolean slotMatched = false;
|
||||
for (GenericStack candidate : in.getPossibleInputs()) {
|
||||
AEKey key = candidate.what().dropSecondary();
|
||||
if (adapter.containsPatternInput(Collections.singleton(key))) {
|
||||
slotMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slotMatched) {
|
||||
return false; // 任一输入槽未匹配则失败
|
||||
}
|
||||
}
|
||||
return true; // 每个输入槽都至少匹配了一个候选输入
|
||||
}
|
||||
|
||||
@Shadow public void saveChanges() {}
|
||||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = input.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.extendedae_plus.mixin.advancedae.helpers;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = AdvPatternProviderLogic.class, remap = false)
|
||||
public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
||||
@Unique
|
||||
private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling";
|
||||
|
||||
@Unique
|
||||
private boolean eap$smartDoubling = false;
|
||||
|
||||
@Override
|
||||
public boolean eap$getSmartDoubling() {
|
||||
return eap$smartDoubling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setSmartDoubling(boolean value) {
|
||||
this.eap$smartDoubling = value;
|
||||
// 立即将开关状态应用到当前 Provider 的样板上,避免等待下一次 updatePatterns
|
||||
try {
|
||||
var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
|
||||
for (IPatternDetails details : list) {
|
||||
if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) {
|
||||
aware.eap$setAllowScaling(value);
|
||||
}
|
||||
}
|
||||
// 触发一次刷新,让网络及时拿到最新状态(也会触发 ICraftingProvider.requestUpdate(mainNode))
|
||||
((AdvPatternProviderLogic) (Object) this).updatePatterns();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readSmartDoublingFromNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updatePatterns", at = @At("TAIL"))
|
||||
private void eap$applySmartDoublingToPatterns(CallbackInfo ci) {
|
||||
try {
|
||||
var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
|
||||
boolean allow = this.eap$smartDoubling;
|
||||
for (IPatternDetails details : list) {
|
||||
if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) {
|
||||
aware.eap$setAllowScaling(allow);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Shadow
|
||||
public void saveChanges() {}
|
||||
|
||||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = input.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.extendedae_plus.mixin.advancedae.menu;
|
||||
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.guisync.GuiSync;
|
||||
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogicHost;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(AdvPatternProviderMenu.class)
|
||||
public abstract class AdvPatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync {
|
||||
@Final
|
||||
@Shadow(remap = false)
|
||||
protected AdvPatternProviderLogic logic;
|
||||
|
||||
// 选择一个未占用的 GUI 同步 id(AE2 已用到 7),这里使用 21 以避冲突
|
||||
@Unique
|
||||
@GuiSync(22)
|
||||
public boolean eap$AdvancedBlocking = false;
|
||||
|
||||
@Inject(method = "broadcastChanges", at = @At("HEAD"))
|
||||
private void eap$syncAdvancedBlocking(CallbackInfo ci) {
|
||||
// 避免@Shadow父类方法,改用公共API:AEBaseMenu#isClientSide()
|
||||
if (!((AEBaseMenu) (Object) this).isClientSide()) {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造器尾注入(public ctor)
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 构造器尾注入(protected ctor with MenuType)
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initAdvancedSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$getAdvancedBlockingSynced() {
|
||||
return this.eap$AdvancedBlocking;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.extendedae_plus.mixin.advancedae.menu;
|
||||
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.guisync.GuiSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||
import net.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogicHost;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(AdvPatternProviderMenu.class)
|
||||
public abstract class AdvPatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
|
||||
@Final
|
||||
@Shadow(remap = false)
|
||||
protected AdvPatternProviderLogic logic;
|
||||
|
||||
@Unique
|
||||
@GuiSync(23)
|
||||
public boolean eap$SmartDoubling = false;
|
||||
|
||||
@Inject(method = "broadcastChanges", at = @At("HEAD"))
|
||||
private void eap$syncSmartDoubling(CallbackInfo ci) {
|
||||
if (!((AEBaseMenu) (Object) this).isClientSide()) {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initSmartSync_Public(int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lnet/pedroksl/advanced_ae/common/logic/AdvPatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initSmartSync_Protected(MenuType menuType, int id, Inventory playerInventory, AdvPatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$getSmartDoublingSynced() {
|
||||
return this.eap$SmartDoubling;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.crafting.CraftingCalculation;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
|
||||
/**
|
||||
* Mixin 用于增加 CraftingCalculation.handlePausing() 中 incTime 的阈值。
|
||||
* 调整 CRAFTING_PAUSE_THRESHOLD 可以控制计算让出控制权的频率。
|
||||
*/
|
||||
@Mixin(value = CraftingCalculation.class, remap = false)
|
||||
public abstract class CraftingCalculationMixin {
|
||||
// 可调节的阈值:在执行 pause 检查前 handlePausing 被调用的次数。
|
||||
// 增大此值可以减少 wait/notify 的频率(提升吞吐量但降低响应速度)。
|
||||
@ModifyConstant(method = "handlePausing", constant = @Constant(intValue = 100))
|
||||
private int modifyIncTimeThreshold(int original) {
|
||||
return ModConfig.INSTANCE.craftingPauseThreshold;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.crafting.pattern.EncodedPatternItem;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
|
@ -20,7 +20,7 @@ public class EncodedPatternItemMixin {
|
|||
// 客户端:在 HoverText 显示样板的编码玩家
|
||||
@Inject(method = "appendHoverText", at = @At("TAIL"))
|
||||
public void epp$appendHoverText(ItemStack stack, Level level, List<Component> lines, TooltipFlag advancedTooltips, CallbackInfo ci){
|
||||
if (stack.hasTag() && ModConfigs.SHOW_ENCOD_PATTERN_PLAYER.get()) {
|
||||
if (stack.hasTag() && ModConfig.INSTANCE.showEncoderPatternPlayer) {
|
||||
CompoundTag tag = stack.getOrCreateTag();
|
||||
String name = tag.getString("encodePlayer");
|
||||
lines.add(Component.translatable("extendedae_plus.pattern.hovertext.player", name).withStyle(ChatFormatting.GRAY));
|
||||
|
|
|
|||
|
|
@ -10,23 +10,23 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(MEStorageMenu.class)
|
||||
public interface MEStorageMenuAccessor {
|
||||
@Accessor("storage")
|
||||
@Accessor(value = "storage", remap = false)
|
||||
@Nullable
|
||||
MEStorage getStorage();
|
||||
|
||||
@Accessor("powerSource")
|
||||
@Accessor(value = "powerSource", remap = false)
|
||||
@Nullable
|
||||
IEnergySource getPowerSource();
|
||||
|
||||
@Accessor("hasPower")
|
||||
@Accessor(value = "hasPower", remap = false)
|
||||
boolean getHasPower();
|
||||
|
||||
// Access client-side config manager mirror used for syncing settings
|
||||
@Accessor("clientCM")
|
||||
@Accessor(value = "clientCM", remap = false)
|
||||
IConfigManager getClientCM();
|
||||
|
||||
// Access server-side config manager
|
||||
@Accessor("serverCM")
|
||||
@Accessor(value = "serverCM", remap = false)
|
||||
@Nullable
|
||||
IConfigManager getServerCM();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(PatternEncodingTermMenu.class)
|
||||
public interface PatternEncodingTermMenuAccessor {
|
||||
@Accessor("encodedPatternSlot")
|
||||
@Accessor(value = "encodedPatternSlot",remap = false)
|
||||
RestrictedInputSlot eap$getEncodedPatternSlot();
|
||||
|
||||
@Accessor("blankPatternSlot")
|
||||
@Accessor(value = "blankPatternSlot",remap = false)
|
||||
RestrictedInputSlot eap$getBlankPatternSlot();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(PatternProviderLogic.class)
|
||||
public interface PatternProviderLogicAccessor {
|
||||
@Accessor("host")
|
||||
@Accessor(value = "host", remap = false)
|
||||
PatternProviderLogicHost eap$host();
|
||||
|
||||
@Accessor("mainNode")
|
||||
@Accessor(value = "mainNode", remap = false)
|
||||
IManagedGridNode eap$mainNode();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ import java.util.Set;
|
|||
|
||||
@Mixin(PatternProviderLogic.class)
|
||||
public interface PatternProviderLogicPatternInputsAccessor {
|
||||
@Accessor("patternInputs")
|
||||
@Accessor(value = "patternInputs",remap = false)
|
||||
Set<AEKey> eap$patternInputs();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(PatternProviderMenu.class)
|
||||
public interface PatternProviderMenuAdvancedAccessor {
|
||||
@Accessor("logic")
|
||||
@Accessor(value = "logic", remap = false)
|
||||
PatternProviderLogic eap$logic();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(CraftingTreeNode.class)
|
||||
public interface CraftingTreeNodeAccessor {
|
||||
@Accessor("what")
|
||||
@Accessor(value = "what", remap = false)
|
||||
AEKey eap$getWhat();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import appeng.crafting.CraftingTreeProcess;
|
|||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import appeng.me.service.CraftingService;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
import com.extendedae_plus.util.PatternScaler;
|
||||
import com.extendedae_plus.util.RequestedAmountHolder;
|
||||
|
|
@ -33,7 +33,8 @@ public abstract class CraftingTreeProcessMixin {
|
|||
@ModifyVariable(
|
||||
method = "<init>(Lappeng/api/networking/crafting/ICraftingService;Lappeng/crafting/CraftingCalculation;Lappeng/api/crafting/IPatternDetails;Lappeng/crafting/CraftingTreeNode;)V",
|
||||
at = @At("HEAD"),
|
||||
argsOnly = true
|
||||
argsOnly = true,
|
||||
remap = false
|
||||
)
|
||||
private static IPatternDetails eap$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) {
|
||||
try {
|
||||
|
|
@ -58,7 +59,7 @@ public abstract class CraftingTreeProcessMixin {
|
|||
|
||||
// 根据配置决定是否在 provider 间轮询分配请求量(默认开启)
|
||||
long perProvider = 1L;
|
||||
if (!ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get()) {
|
||||
if (!ModConfig.INSTANCE.providerRoundRobinEnable) {
|
||||
// 关闭轮询:直接使用完整请求量,不需要查询 provider 列表
|
||||
perProvider = requested;
|
||||
if (perProvider <= 0) perProvider = 1L;
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ import appeng.client.gui.style.TextAlignment;
|
|||
import appeng.menu.slot.AppEngSlot;
|
||||
import com.extendedae_plus.api.ExPatternPageAccessor;
|
||||
import com.extendedae_plus.content.ClientPatternHighlightStore;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
|
||||
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.util.GuiUtil;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
|
|
@ -39,38 +40,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
@Mixin(AEBaseScreen.class)
|
||||
public abstract class AEBaseScreenMixin {
|
||||
|
||||
@Unique
|
||||
private static int eap$getIntField(Object self, String name, int def) {
|
||||
Class<?> c = self.getClass();
|
||||
while (c != null && c != Object.class) {
|
||||
try {
|
||||
var f = c.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(self);
|
||||
if (v instanceof Integer i) return i;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Font eap$getFont(Object self) {
|
||||
Class<?> c = self.getClass();
|
||||
while (c != null && c != Object.class) {
|
||||
try {
|
||||
var f = c.getDeclaredField("font");
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(self);
|
||||
if (v instanceof Font font) return font;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return net.minecraft.client.Minecraft.getInstance().font;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private ScreenStyle eap$getStyle(Object self) {
|
||||
try {
|
||||
|
|
@ -78,8 +47,7 @@ public abstract class AEBaseScreenMixin {
|
|||
f.setAccessible(true);
|
||||
Object v = f.get(self);
|
||||
if (v instanceof ScreenStyle s) return s;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +75,10 @@ public abstract class AEBaseScreenMixin {
|
|||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
|
|
@ -137,12 +109,46 @@ public abstract class AEBaseScreenMixin {
|
|||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送(打开供应器UI)
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorOpenProviderC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int eap$getIntField(Object self, String name, int def) {
|
||||
Class<?> c = self.getClass();
|
||||
while (c != null && c != Object.class) {
|
||||
try {
|
||||
var f = c.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(self);
|
||||
if (v instanceof Integer i) return i;
|
||||
} catch (Throwable ignored) {}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static Font eap$getFont(Object self) {
|
||||
Class<?> c = self.getClass();
|
||||
while (c != null && c != Object.class) {
|
||||
try {
|
||||
var f = c.getDeclaredField("font");
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(self);
|
||||
if (v instanceof Font font) return font;
|
||||
} catch (Throwable ignored) {}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return net.minecraft.client.Minecraft.getInstance().font;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写renderSlot方法,为所有可见的样板槽位添加数量显示
|
||||
*/
|
||||
|
|
@ -178,14 +184,17 @@ public abstract class AEBaseScreenMixin {
|
|||
|
||||
try {
|
||||
var details = PatternDetailsHelper.decodePattern(itemStack, Minecraft.getInstance().level, false);
|
||||
if (details != null && details.getOutputs() != null && details.getOutputs().length > 0) {
|
||||
AEKey key = details.getOutputs()[0].what();
|
||||
if (key != null && ClientPatternHighlightStore.hasHighlight(key)) {
|
||||
GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y);
|
||||
try {
|
||||
if (details != null && details.getOutputs() != null && details.getOutputs().length > 0) {
|
||||
AEKey key = details.getOutputs()[0].what();
|
||||
if (key != null && ClientPatternHighlightStore.hasHighlight(key)) {
|
||||
try {
|
||||
GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
}
|
||||
} catch (Throwable ignore) {}
|
||||
} catch (Throwable ignore) {}
|
||||
}
|
||||
|
||||
// 在 AEBaseScreen.drawText 完成某个文本绘制后,若该文本为“样板”标签,则紧接着绘制页码。
|
||||
|
|
@ -303,20 +312,19 @@ public abstract class AEBaseScreenMixin {
|
|||
}
|
||||
|
||||
|
||||
@Shadow
|
||||
protected void setTextContent(String id, Component content) {}
|
||||
@Shadow(remap = false)
|
||||
protected void setTextContent(String id, Component content) {};
|
||||
|
||||
@Inject(method = "updateBeforeRender", at = @At("RETURN"), remap = false)
|
||||
private void onUpdateBeforeRender(CallbackInfo ci) {
|
||||
try {
|
||||
AEBaseScreen<?> self = (AEBaseScreen<?>) (Object) this;
|
||||
if (self instanceof PatternProviderScreen screen) {
|
||||
if (self instanceof PatternProviderScreen screen){
|
||||
Component t = screen.getTitle();
|
||||
if (t != null && !t.getString().isEmpty()) {
|
||||
this.setTextContent(AEBaseScreen.TEXT_ID_DIALOG_TITLE, t);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,320 @@
|
|||
package com.extendedae_plus.mixin.ae2.client.gui;
|
||||
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.implementations.InterfaceScreen;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import com.extendedae_plus.NewIcon;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.InterfaceAdjustConfigAmountC2SPacket;
|
||||
import appeng.menu.SlotSemantics;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor;
|
||||
import com.extendedae_plus.mixin.accessor.ScreenAccessor;
|
||||
import com.glodblock.github.extendedae.client.button.ActionEPPButton;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import 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;
|
||||
|
||||
/**
|
||||
* 在 AE2 的 ME 接口界面注入倍增/除法按钮(x2/÷2、x5/÷5、x10/÷10)。
|
||||
* 点击逻辑暂时留空,仅完成按钮创建、注册与布局维护。
|
||||
*/
|
||||
@Mixin(AEBaseScreen.class)
|
||||
public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
|
||||
|
||||
@Unique private ActionEPPButton eap$x2Button;
|
||||
@Unique private ActionEPPButton eap$divideBy2Button;
|
||||
@Unique private ActionEPPButton eap$x5Button;
|
||||
@Unique private ActionEPPButton eap$divideBy5Button;
|
||||
@Unique private ActionEPPButton eap$x10Button;
|
||||
@Unique private ActionEPPButton eap$divideBy10Button;
|
||||
|
||||
@Unique private int eap$lastLeftPos = -1;
|
||||
@Unique private int eap$lastTopPos = -1;
|
||||
@Unique private int eap$lastImageWidth = -1;
|
||||
@Unique private int eap$lastImageHeight = -1;
|
||||
// 记录最近一次在 CONFIG 槽位区域内悬停到的配置槽索引,
|
||||
// 以便当鼠标移到按钮上导致 hoveredSlot 为空时仍能进行操作。
|
||||
@Unique private int eap$lastConfigIndex = -1;
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"))
|
||||
private void eap$addScaleButtons(CallbackInfo ci) {
|
||||
// 仅在 AE2 接口界面或 ExtendedAE 扩展接口界面中添加
|
||||
if (!eap$isSupportedInterfaceScreen()) {
|
||||
return;
|
||||
}
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] init tail reached, preparing scale buttons."); } catch (Throwable ignored) {}
|
||||
// 避免重复创建
|
||||
if (eap$x2Button == null) {
|
||||
eap$x2Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(false, 2);
|
||||
}, NewIcon.MULTIPLY2);
|
||||
eap$x2Button.setTooltip(null);
|
||||
eap$x2Button.setVisibility(true);
|
||||
}
|
||||
if (eap$divideBy2Button == null) {
|
||||
eap$divideBy2Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(true, 2);
|
||||
}, NewIcon.DIVIDE2);
|
||||
eap$divideBy2Button.setTooltip(null);
|
||||
eap$divideBy2Button.setVisibility(true);
|
||||
}
|
||||
if (eap$x5Button == null) {
|
||||
eap$x5Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(false, 5);
|
||||
}, NewIcon.MULTIPLY5);
|
||||
eap$x5Button.setTooltip(null);
|
||||
eap$x5Button.setVisibility(true);
|
||||
}
|
||||
if (eap$divideBy5Button == null) {
|
||||
eap$divideBy5Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(true, 5);
|
||||
}, NewIcon.DIVIDE5);
|
||||
eap$divideBy5Button.setTooltip(null);
|
||||
eap$divideBy5Button.setVisibility(true);
|
||||
}
|
||||
if (eap$x10Button == null) {
|
||||
eap$x10Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(false, 10);
|
||||
}, NewIcon.MULTIPLY10);
|
||||
eap$x10Button.setTooltip(null);
|
||||
eap$x10Button.setVisibility(true);
|
||||
}
|
||||
if (eap$divideBy10Button == null) {
|
||||
eap$divideBy10Button = new ActionEPPButton((b) -> {
|
||||
eap$sendAdjustForAllConfigs(true, 10);
|
||||
}, NewIcon.DIVIDE10);
|
||||
eap$divideBy10Button.setTooltip(null);
|
||||
eap$divideBy10Button.setVisibility(true);
|
||||
}
|
||||
|
||||
// 注册到渲染与交互列表
|
||||
var accessor = (ScreenAccessor) (Object) this;
|
||||
if (!accessor.eap$getRenderables().contains(eap$divideBy2Button)) accessor.eap$getRenderables().add(eap$divideBy2Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$divideBy2Button)) accessor.eap$getChildren().add(eap$divideBy2Button);
|
||||
if (!accessor.eap$getRenderables().contains(eap$x2Button)) accessor.eap$getRenderables().add(eap$x2Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$x2Button)) accessor.eap$getChildren().add(eap$x2Button);
|
||||
if (!accessor.eap$getRenderables().contains(eap$divideBy5Button)) accessor.eap$getRenderables().add(eap$divideBy5Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$divideBy5Button)) accessor.eap$getChildren().add(eap$divideBy5Button);
|
||||
if (!accessor.eap$getRenderables().contains(eap$x5Button)) accessor.eap$getRenderables().add(eap$x5Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$x5Button)) accessor.eap$getChildren().add(eap$x5Button);
|
||||
if (!accessor.eap$getRenderables().contains(eap$divideBy10Button)) accessor.eap$getRenderables().add(eap$divideBy10Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$divideBy10Button)) accessor.eap$getChildren().add(eap$divideBy10Button);
|
||||
if (!accessor.eap$getRenderables().contains(eap$x10Button)) accessor.eap$getRenderables().add(eap$x10Button);
|
||||
if (!accessor.eap$getChildren().contains(eap$x10Button)) accessor.eap$getChildren().add(eap$x10Button);
|
||||
|
||||
// 初次定位
|
||||
eap$relayoutButtons();
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] buttons added and laid out."); } catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Inject(method = "containerTick", at = @At("TAIL"))
|
||||
private void eap$ensureButtons(CallbackInfo ci) {
|
||||
if (!eap$isSupportedInterfaceScreen()) {
|
||||
return;
|
||||
}
|
||||
var accessor = (ScreenAccessor) (Object) this;
|
||||
// 若被其他模组清空,补回
|
||||
if (eap$divideBy2Button != null && !accessor.eap$getRenderables().contains(eap$divideBy2Button)) {
|
||||
accessor.eap$getRenderables().add(eap$divideBy2Button);
|
||||
accessor.eap$getChildren().add(eap$divideBy2Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added divide2 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (eap$x2Button != null && !accessor.eap$getRenderables().contains(eap$x2Button)) {
|
||||
accessor.eap$getRenderables().add(eap$x2Button);
|
||||
accessor.eap$getChildren().add(eap$x2Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added x2 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (eap$divideBy5Button != null && !accessor.eap$getRenderables().contains(eap$divideBy5Button)) {
|
||||
accessor.eap$getRenderables().add(eap$divideBy5Button);
|
||||
accessor.eap$getChildren().add(eap$divideBy5Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added divide5 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (eap$x5Button != null && !accessor.eap$getRenderables().contains(eap$x5Button)) {
|
||||
accessor.eap$getRenderables().add(eap$x5Button);
|
||||
accessor.eap$getChildren().add(eap$x5Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added x5 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (eap$divideBy10Button != null && !accessor.eap$getRenderables().contains(eap$divideBy10Button)) {
|
||||
accessor.eap$getRenderables().add(eap$divideBy10Button);
|
||||
accessor.eap$getChildren().add(eap$divideBy10Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added divide10 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (eap$x10Button != null && !accessor.eap$getRenderables().contains(eap$x10Button)) {
|
||||
accessor.eap$getRenderables().add(eap$x10Button);
|
||||
accessor.eap$getChildren().add(eap$x10Button);
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] re-added x10 button to renderables."); } catch (Throwable ignored) {}
|
||||
}
|
||||
// 尺寸变化时重新定位
|
||||
int curLeft = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getLeftPos();
|
||||
int curTop = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
|
||||
int curImgW = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
|
||||
int curImgH = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageHeight();
|
||||
if (curLeft != eap$lastLeftPos || curTop != eap$lastTopPos || curImgW != eap$lastImageWidth || curImgH != eap$lastImageHeight) {
|
||||
eap$lastLeftPos = curLeft;
|
||||
eap$lastTopPos = curTop;
|
||||
eap$lastImageWidth = curImgW;
|
||||
eap$lastImageHeight = curImgH;
|
||||
eap$relayoutButtons();
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] relayout due to bounds change: left={}, top={}, w={}, h={}", curLeft, curTop, curImgW, curImgH); } catch (Throwable ignored) {}
|
||||
}
|
||||
// 每帧根据 hoveredSlot 刷新最近一次的配置槽索引
|
||||
eap$updateLastConfigFromHover();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$sendAdjustForHoveredConfig(boolean divide, int factor) {
|
||||
try {
|
||||
// 仅在 InterfaceScreen 中生效
|
||||
if (!eap$isSupportedInterfaceScreen()) {
|
||||
return;
|
||||
}
|
||||
// 获取悬停槽位
|
||||
Slot hovered = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getHoveredSlot();
|
||||
// 获取菜单与配置槽列表
|
||||
var screen = (AEBaseScreen<?>) (Object) this;
|
||||
var menu = screen.getMenu();
|
||||
if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) {
|
||||
return;
|
||||
}
|
||||
var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG);
|
||||
if (configSlots == null || configSlots.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先根据 hoveredSlot 解析索引;若 hovered 为空,则回退使用最近一次的配置槽索引
|
||||
Integer slotFieldObj = null;
|
||||
if (hovered != null) {
|
||||
for (var s : configSlots) {
|
||||
if (s == hovered) {
|
||||
// 反射读取 AppEngSlot#slot
|
||||
try {
|
||||
var f = s.getClass().getDeclaredField("slot");
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(s);
|
||||
if (v instanceof Integer i) {
|
||||
slotFieldObj = i;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
if (slotFieldObj == null) {
|
||||
// 回退:使用列表位置当作索引
|
||||
slotFieldObj = configSlots.indexOf(s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int slotField = -1;
|
||||
if (slotFieldObj != null) {
|
||||
slotField = slotFieldObj;
|
||||
} else if (eap$lastConfigIndex >= 0 && eap$lastConfigIndex < configSlots.size()) {
|
||||
slotField = eap$lastConfigIndex;
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] Using last hovered config index: {}", slotField); } catch (Throwable ignored) {}
|
||||
}
|
||||
if (slotField < 0) {
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] No hovered slot and no last config index; ignoring adjust."); } catch (Throwable ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送到服务端
|
||||
ModNetwork.CHANNEL.sendToServer(new InterfaceAdjustConfigAmountC2SPacket(slotField, divide, factor));
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$sendAdjustForAllConfigs(boolean divide, int factor) {
|
||||
try {
|
||||
if (!eap$isSupportedInterfaceScreen()) {
|
||||
return;
|
||||
}
|
||||
// 直接发送 -1 表示对所有 CONFIG 槽生效
|
||||
ModNetwork.CHANNEL.sendToServer(new InterfaceAdjustConfigAmountC2SPacket(-1, divide, factor));
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean eap$isSupportedInterfaceScreen() {
|
||||
// AE2 原版接口界面
|
||||
if (((Object) this) instanceof InterfaceScreen) {
|
||||
return true;
|
||||
}
|
||||
// ExtendedAE 扩展接口界面(使用类名判断避免编译期硬依赖)
|
||||
try {
|
||||
String cn = ((Object) this).getClass().getName();
|
||||
if ("com.glodblock.github.extendedae.client.gui.GuiExInterface".equals(cn)) {
|
||||
return true;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$relayoutButtons() {
|
||||
try {
|
||||
int leftPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getLeftPos();
|
||||
int topPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
|
||||
int imageWidth = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
|
||||
// 按照样板供应器界面一致的布局:界面右缘外侧竖排
|
||||
int bx = leftPos + imageWidth + 1;
|
||||
int by = topPos + 20;
|
||||
int spacing = 22;
|
||||
if (eap$divideBy2Button != null) { eap$divideBy2Button.setX(bx); eap$divideBy2Button.setY(by); }
|
||||
if (eap$x2Button != null) { eap$x2Button.setX(bx); eap$x2Button.setY(by + spacing); }
|
||||
if (eap$divideBy5Button != null) { eap$divideBy5Button.setX(bx); eap$divideBy5Button.setY(by + spacing * 2); }
|
||||
if (eap$x5Button != null) { eap$x5Button.setX(bx); eap$x5Button.setY(by + spacing * 3); }
|
||||
if (eap$divideBy10Button != null) { eap$divideBy10Button.setX(bx); eap$divideBy10Button.setY(by + spacing * 4); }
|
||||
if (eap$x10Button != null) { eap$x10Button.setX(bx); eap$x10Button.setY(by + spacing * 5); }
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$updateLastConfigFromHover() {
|
||||
try {
|
||||
if (!(((Object) this) instanceof InterfaceScreen)) {
|
||||
return;
|
||||
}
|
||||
Slot hovered = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getHoveredSlot();
|
||||
if (hovered == null) {
|
||||
return;
|
||||
}
|
||||
var screen = (AEBaseScreen<?>) (Object) this;
|
||||
var menu = screen.getMenu();
|
||||
if (!(menu instanceof appeng.menu.implementations.InterfaceMenu interfaceMenu)) {
|
||||
return;
|
||||
}
|
||||
var configSlots = interfaceMenu.getSlots(SlotSemantics.CONFIG);
|
||||
if (configSlots == null || configSlots.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 在 CONFIG 槽列表中定位 hovered 对应的索引
|
||||
Integer idx = null;
|
||||
for (var s : configSlots) {
|
||||
if (s == hovered) {
|
||||
try {
|
||||
var f = s.getClass().getDeclaredField("slot");
|
||||
f.setAccessible(true);
|
||||
Object v = f.get(s);
|
||||
if (v instanceof Integer i) {
|
||||
idx = i;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
if (idx == null) {
|
||||
idx = configSlots.indexOf(s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx != null && idx >= 0) {
|
||||
if (eap$lastConfigIndex != idx) {
|
||||
eap$lastConfigIndex = idx;
|
||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] lastConfigIndex updated: {}", eap$lastConfigIndex); } catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,10 +7,10 @@ import appeng.client.gui.style.ScreenStyle;
|
|||
import appeng.client.gui.style.WidgetStyle;
|
||||
import appeng.client.gui.widgets.IconButton;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.mixin.accessor.AbstractContainerScreenAccessor;
|
||||
import com.extendedae_plus.mixin.accessor.ScreenAccessor;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.AEBaseScreenAccessor;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import appeng.menu.implementations.PatternProviderMenu;
|
|||
import com.extendedae_plus.api.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
|
|
@ -65,7 +65,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||
LOGGER.debug("[EAP] Click advanced blocking toggle: send C2S");
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleAdvancedBlockingC2SPacket());
|
||||
}
|
||||
) {
|
||||
|
|
@ -80,7 +79,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
}
|
||||
};
|
||||
// 初始化后立刻对齐当前@GuiSync状态,避免首帧显示不一致
|
||||
LOGGER.debug("[EAP] Screen init: initial synced={} -> set button", this.eap$AdvancedBlockingEnabled);
|
||||
this.eap$AdvancedBlockingToggle.set(this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO);
|
||||
|
||||
this.addToLeftToolbar(this.eap$AdvancedBlockingToggle);
|
||||
|
|
@ -98,7 +96,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
Settings.BLOCKING_MODE,
|
||||
this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
|
||||
(btn, backwards) -> {
|
||||
LOGGER.debug("[EAP] Click smart doubling toggle: send C2S");
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
|
|
@ -125,7 +122,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (adv): desired={}", desired);
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
|
@ -135,7 +131,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
|
||||
desired2 = sync2.eap$getSmartDoublingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (dbl): desired={}", desired2);
|
||||
this.eap$SmartDoublingEnabled = desired2;
|
||||
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.Collections;
|
|||
@Mixin(value = PatternProviderLogic.class, remap = false)
|
||||
public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
|
||||
@Unique
|
||||
private static final String EPP_ADV_BLOCKING_KEY = "epp_advanced_blocking";
|
||||
private static final String EAP_ADV_BLOCKING_KEY = "eap_advanced_blocking";
|
||||
|
||||
@Unique
|
||||
private boolean eap$advancedBlocking = false;
|
||||
|
|
@ -39,13 +39,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
|||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeAdvancedToNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
tag.putBoolean(EPP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readAdvancedFromNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
if (tag.contains(EPP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = tag.getBoolean(EPP_ADV_BLOCKING_KEY);
|
||||
if (tag.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = tag.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,13 +95,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
|||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$advancedBlocking);
|
||||
output.putBoolean("eap_advanced_blocking", this.eap$advancedBlocking);
|
||||
output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains("eap_advanced_blocking")) {
|
||||
this.eap$advancedBlocking = input.getBoolean("eap_advanced_blocking");
|
||||
if (input.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||
this.eap$advancedBlocking = input.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
@Mixin(value = PatternProviderLogic.class, remap = false)
|
||||
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
||||
@Unique
|
||||
private static final String EPP_SMART_DOUBLING_KEY = "epp_smart_doubling";
|
||||
private static final String EAP_SMART_DOUBLING_KEY = "eap_smart_doubling";
|
||||
|
||||
@Unique
|
||||
private boolean eap$smartDoubling = false;
|
||||
|
|
@ -47,13 +47,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
|||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||
private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
tag.putBoolean(EPP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||
private void eap$readSmartDoublingFromNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
if (tag.contains(EPP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = tag.getBoolean(EPP_SMART_DOUBLING_KEY);
|
||||
if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,13 +77,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
|||
@Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL"))
|
||||
private void onExportSettings(CompoundTag output, CallbackInfo ci) {
|
||||
System.out.println(this.eap$smartDoubling);
|
||||
output.putBoolean("eap_smart_doubling", this.eap$smartDoubling);
|
||||
output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "importSettings(Lnet/minecraft/nbt/CompoundTag;Lnet/minecraft/world/entity/player/Player;)V", at = @At("TAIL"))
|
||||
private void onImportSettings(CompoundTag input, Player player, CallbackInfo ci) {
|
||||
if (input.contains("eap_smart_doubling")) {
|
||||
this.eap$smartDoubling = input.getBoolean("eap_smart_doubling");
|
||||
if (input.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = input.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||
// 持久化到 world
|
||||
this.saveChanges();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.extendedae_plus.mixin.ae2.menu;
|
||||
|
||||
import appeng.api.stacks.AEFluidKey;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.menu.me.crafting.CraftConfirmMenu;
|
||||
import appeng.menu.me.crafting.CraftingPlanSummary;
|
||||
import appeng.menu.me.crafting.CraftingPlanSummaryEntry;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* Inject into CraftConfirmMenu.goBack() after it finishes and read the plan entries.
|
||||
*/
|
||||
@Mixin(CraftConfirmMenu.class)
|
||||
public class CraftConfirmMenuGoBackMixin {
|
||||
|
||||
@Inject(method = "goBack", at = @At("RETURN"), remap = false)
|
||||
private void afterGoBack(CallbackInfo ci) {
|
||||
CraftConfirmMenu self = (CraftConfirmMenu) (Object) this;
|
||||
|
||||
try {
|
||||
// 只在客户端执行此逻辑,服务端直接返回
|
||||
if (!self.isClientSide()) return;
|
||||
|
||||
// 检测本地客户端是否按下了 shift 键
|
||||
boolean shiftDown = false;
|
||||
try {
|
||||
shiftDown = Screen.hasShiftDown();
|
||||
} catch (Throwable ignored) {}
|
||||
if (!shiftDown) return;
|
||||
|
||||
// 获取合成计划摘要
|
||||
CraftingPlanSummary plan = self.getPlan();
|
||||
if (plan == null) return;
|
||||
|
||||
// 获取合成计划条目列表
|
||||
List<CraftingPlanSummaryEntry> entries = plan.getEntries();
|
||||
if (entries == null || entries.isEmpty()) return;
|
||||
|
||||
// 仅在按住 shift 时为缺失的条目添加 JEI 书签
|
||||
for (CraftingPlanSummaryEntry entry : entries) {
|
||||
if (entry.getMissingAmount() > 0) {
|
||||
var what = entry.getWhat();
|
||||
if (what instanceof AEItemKey aeItemKey) {
|
||||
JeiRuntimeProxy.addBookmark(aeItemKey.getReadOnlyStack());
|
||||
} else if (what instanceof AEFluidKey aeFluidKey) {
|
||||
JeiRuntimeProxy.addBookmark(aeFluidKey.toStack(1000));
|
||||
} else if (ModList.get().isLoaded("appmek") && ModList.get().isLoaded("mekanism")) {
|
||||
try {
|
||||
if (what != null) {
|
||||
// avoid compile-time dependency on MekanismKey by reflection
|
||||
Class<?> mekanismKeyCls = Class.forName("me.ramidzkh.mekae2.ae2.MekanismKey");
|
||||
if (mekanismKeyCls.isInstance(what)) {
|
||||
java.lang.reflect.Method m = mekanismKeyCls.getMethod("getStack");
|
||||
Object stack = m.invoke(what);
|
||||
JeiRuntimeProxy.addBookmark(stack);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import appeng.menu.slot.RestrictedInputSlot;
|
|||
import com.extendedae_plus.mixin.ae2.accessor.MEStorageMenuAccessor;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -28,7 +29,8 @@ public abstract class PatternEncodingTermMenuMixin {
|
|||
@Unique
|
||||
private boolean eap$blankAutoFilled = false;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Shadow(remap = false)
|
||||
private RestrictedInputSlot blankPatternSlot;
|
||||
|
||||
@Unique
|
||||
|
|
@ -87,7 +89,7 @@ public abstract class PatternEncodingTermMenuMixin {
|
|||
}
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;Z)V",
|
||||
at = @At("TAIL"))
|
||||
at = @At("TAIL"), remap = false)
|
||||
private void eap$autoFillBlankPattern(MenuType<?> menuType, int id, Inventory ip,
|
||||
IPatternTerminalMenuHost host, boolean bindInventory,
|
||||
CallbackInfo ci) {
|
||||
|
|
@ -95,7 +97,7 @@ public abstract class PatternEncodingTermMenuMixin {
|
|||
}
|
||||
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/IPatternTerminalMenuHost;)V",
|
||||
at = @At("TAIL"))
|
||||
at = @At("TAIL"), remap = false)
|
||||
private void eap$autoFillCtor3(int id, Inventory ip, IPatternTerminalMenuHost host, CallbackInfo ci) {
|
||||
eap$tryFill(host, ip);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,19 +9,20 @@ import com.extendedae_plus.api.AdvancedBlockingHolder;
|
|||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
@Mixin(PatternProviderMenu.class)
|
||||
public abstract class PatternProviderMenuAdvancedMixin implements PatternProviderMenuAdvancedSync {
|
||||
@Shadow
|
||||
@Final
|
||||
@Shadow(remap = false)
|
||||
protected PatternProviderLogic logic;
|
||||
|
||||
// 选择一个未占用的 GUI 同步 id(AE2 已用到 7),这里使用 20 以避冲突
|
||||
|
|
@ -36,26 +37,23 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
|
|||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
LOGGER.debug("[EAP] Menu broadcastChanges HEAD: eap$AdvancedBlocking={}", this.eap$AdvancedBlocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构造器尾注入(public ctor)
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initAdvancedSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof AdvancedBlockingHolder holder) {
|
||||
this.eap$AdvancedBlocking = holder.eap$getAdvancedBlocking();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing advanced sync", t);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
// 构造器尾注入(protected ctor with MenuType)
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initAdvancedSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
|
|
@ -71,13 +69,4 @@ public abstract class PatternProviderMenuAdvancedMixin implements PatternProvide
|
|||
public boolean eap$getAdvancedBlockingSynced() {
|
||||
return this.eap$AdvancedBlocking;
|
||||
}
|
||||
|
||||
// 调试:当 Screen 每帧读取这些 getter 时打印,验证 Mixin 是否生效
|
||||
@Inject(method = "getBlockingMode", at = @At("HEAD"), remap = false)
|
||||
private void eap$debug_getBlockingMode(CallbackInfoReturnable<?> cir) {
|
||||
}
|
||||
|
||||
@Inject(method = "getShowInAccessTerminal", at = @At("HEAD"), remap = false)
|
||||
private void eap$debug_getShowInAccessTerminal(CallbackInfoReturnable<?> cir) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
|||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -20,7 +21,8 @@ import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
|||
|
||||
@Mixin(PatternProviderMenu.class)
|
||||
public abstract class PatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
|
||||
@Shadow
|
||||
@Final
|
||||
@Shadow(remap = false)
|
||||
protected PatternProviderLogic logic;
|
||||
|
||||
@Unique
|
||||
|
|
@ -33,12 +35,11 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
|
|||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
LOGGER.debug("[EAP] Menu broadcastChanges HEAD: eap$SmartDoubling={}", this.eap$SmartDoubling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initSmartSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
|
|
@ -50,7 +51,7 @@ public abstract class PatternProviderMenuDoublingMixin implements PatternProvide
|
|||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"), remap = false)
|
||||
private void eap$initSmartSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.extendedae_plus.mixin.extendedae.accessor;
|
||||
|
||||
import appeng.client.gui.widgets.AETextField;
|
||||
import appeng.client.gui.widgets.Scrollbar;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
|
@ -20,4 +21,7 @@ public interface GuiExPatternTerminalAccessor {
|
|||
|
||||
@Accessor("rows")
|
||||
ArrayList<?> getRows();
|
||||
|
||||
@Accessor("searchOutField")
|
||||
AETextField getSearchOutField();
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import appeng.client.gui.style.ScreenStyle;
|
|||
import appeng.menu.SlotSemantics;
|
||||
import com.extendedae_plus.NewIcon;
|
||||
import com.extendedae_plus.api.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.glodblock.github.extendedae.client.button.ActionEPPButton;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import com.glodblock.github.extendedae.container.ContainerExPatternProvider;
|
||||
|
|
@ -64,7 +64,7 @@ public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<Co
|
|||
private int getMaxPage() {
|
||||
// 优先使用配置倍数
|
||||
try {
|
||||
int cfg = ModConfigs.PAGE_MULTIPLIER.get();
|
||||
int cfg = ModConfig.INSTANCE.pageMultiplier;
|
||||
if (cfg > 1) return cfg;
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
|
|
@ -123,7 +123,7 @@ public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<Co
|
|||
// 计算并下发 maxPage(配置优先,其次按槽位总数计算)
|
||||
int totalSlots = this.getMenu().getSlots(SlotSemantics.ENCODED_PATTERN).size();
|
||||
int cfgPages = 1;
|
||||
try { cfgPages = Math.max(1, ModConfigs.PAGE_MULTIPLIER.get()); } catch (Throwable ignored) {}
|
||||
try { cfgPages = Math.max(1, ModConfig.INSTANCE.pageMultiplier); } catch (Throwable ignored) {}
|
||||
int calcPages = Math.max(1, (int) Math.ceil(totalSlots / (double) SLOTS_PER_PAGE));
|
||||
int desiredMaxPage = Math.max(cfgPages, calcPages);
|
||||
LOGGER.info("[EAP] GuiExPatternProvider init: totalSlots={}, cfgPages={}, calcPages={}, desiredMaxPage={}", totalSlots, cfgPages, calcPages, desiredMaxPage);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import appeng.client.gui.style.ScreenStyle;
|
|||
import appeng.client.gui.widgets.AETextField;
|
||||
import appeng.client.gui.widgets.IconButton;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.OpenProviderUiC2SPacket;
|
||||
import com.extendedae_plus.util.GuiUtil;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
|
|
@ -259,7 +259,7 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
private void injectConstructor(CallbackInfo ci) {
|
||||
// 根据配置初始化默认显示/隐藏状态
|
||||
try {
|
||||
this.eap$showSlots = ModConfigs.PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT.get();
|
||||
this.eap$showSlots = ModConfig.INSTANCE.patternTerminalShowSlotsDefault;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
// 创建切换槽位显示的按钮
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.extendedae_plus.mixin.extendedae.common;
|
||||
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.glodblock.github.extendedae.common.parts.PartExPatternProvider;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -18,7 +18,7 @@ public abstract class PartExPatternProviderMixin {
|
|||
index = 2
|
||||
)
|
||||
private int eap$multiplyCapacity(int original) {
|
||||
int mult = ModConfigs.PAGE_MULTIPLIER.get();
|
||||
int mult = ModConfig.INSTANCE.pageMultiplier;
|
||||
if (mult < 1) mult = 1;
|
||||
if (mult > 64) mult = 64;
|
||||
return Math.max(1, original) * mult;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.extendedae_plus.mixin.extendedae.common;
|
||||
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.glodblock.github.extendedae.common.tileentities.TileExPatternProvider;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -18,7 +18,7 @@ public abstract class TileExPatternProviderMixin {
|
|||
index = 2
|
||||
)
|
||||
private int eap$multiplyCapacity(int original) {
|
||||
int mult = ModConfigs.PAGE_MULTIPLIER.get();
|
||||
int mult = ModConfig.INSTANCE.pageMultiplier;
|
||||
if (mult < 1) mult = 1;
|
||||
if (mult > 64) mult = 64;
|
||||
return Math.max(1, original) * mult;
|
||||
|
|
|
|||
|
|
@ -134,10 +134,12 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
|
|||
} else {
|
||||
for (var stack : stacks) {
|
||||
if (stack != null) {
|
||||
long upper = 999999L * stack.what().getAmountPerUnit();
|
||||
if (stack.amount() * scale > upper) {
|
||||
long amt = stack.amount();
|
||||
// 先检查乘法是否会导致超出 Long.MAX_VALUE,避免溢出
|
||||
if (amt > Integer.MAX_VALUE / scale) {
|
||||
return false;
|
||||
}
|
||||
// 已移除原有的业务上限检查(999999 * amountPerUnit),仅保留溢出检查
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -150,7 +152,18 @@ public abstract class ContainerExPatternProviderMixin extends PatternProviderMen
|
|||
var stack = src[i];
|
||||
if (stack != null) {
|
||||
long amt = stack.amount();
|
||||
long newAmt = div ? (amt / scale) : (amt * scale);
|
||||
long newAmt;
|
||||
if (div) {
|
||||
newAmt = amt / scale;
|
||||
} else {
|
||||
// 防御性检查:虽然上游已检查过,但在此处再次确保不会溢出
|
||||
if (amt > Integer.MAX_VALUE / scale) {
|
||||
// 遇到潜在溢出时跳过该项(保持原样为 null),调用方已通过 eap$checkModify 避免进入此分支
|
||||
dst[i] = null;
|
||||
continue;
|
||||
}
|
||||
newAmt = amt * scale;
|
||||
}
|
||||
dst[i] = new GenericStack(stack.what(), newAmt);
|
||||
} else {
|
||||
dst[i] = null;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public abstract class ContainerWirelessExPatternTerminalMixin implements IAction
|
|||
private Player epp$player;
|
||||
|
||||
// 明确目标构造签名:<init>(int, Inventory, HostWirelessExPAT)
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/common/me/itemhost/HostWirelessExPAT;)V", at = @At("TAIL"), require = 0)
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lcom/glodblock/github/extendedae/common/me/itemhost/HostWirelessExPAT;)V", at = @At("TAIL"), require = 0, remap = false)
|
||||
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, HostWirelessExPAT host, CallbackInfo ci) {
|
||||
this.epp$player = playerInventory.player;
|
||||
// 注册上传动作:参数顺序必须与客户端 CGenericPacket 保持一致
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
|
||||
@Mixin(BookmarkOverlay.class)
|
||||
public interface BookmarkOverlayAccessor {
|
||||
@Accessor("bookmarkList")
|
||||
@Accessor(value = "bookmarkList", remap = false)
|
||||
BookmarkList eap$getBookmarkList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import appeng.menu.AEBaseMenu;
|
|||
import appeng.menu.locator.MenuLocators;
|
||||
import appeng.menu.me.crafting.CraftingCPUMenu;
|
||||
import appeng.parts.AEBasePart;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
||||
import com.extendedae_plus.util.PatternProviderDataUtil;
|
||||
import com.glodblock.github.extendedae.util.FCClientUtil;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.menu.implementations.InterfaceMenu;
|
||||
import appeng.menu.SlotSemantics;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S:调整 ME 接口配置槽位(标记物品)的数量。
|
||||
* 支持按因子倍增或整除,且保持最小值为 1。
|
||||
*/
|
||||
public class InterfaceAdjustConfigAmountC2SPacket {
|
||||
private final int slotIndex; // 配置槽位索引(ConfigInventory 索引)
|
||||
private final boolean divide; // true 表示做除法,否则做乘法
|
||||
private final int factor; // 因子:2/5/10
|
||||
|
||||
public InterfaceAdjustConfigAmountC2SPacket(int slotIndex, boolean divide, int factor) {
|
||||
this.slotIndex = slotIndex;
|
||||
this.divide = divide;
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
public static void encode(InterfaceAdjustConfigAmountC2SPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(msg.slotIndex);
|
||||
buf.writeBoolean(msg.divide);
|
||||
buf.writeVarInt(msg.factor);
|
||||
}
|
||||
|
||||
public static InterfaceAdjustConfigAmountC2SPacket decode(FriendlyByteBuf buf) {
|
||||
int slot = buf.readVarInt();
|
||||
boolean div = buf.readBoolean();
|
||||
int factor = buf.readVarInt();
|
||||
return new InterfaceAdjustConfigAmountC2SPacket(slot, div, factor);
|
||||
|
||||
}
|
||||
|
||||
public static void handle(InterfaceAdjustConfigAmountC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
// 支持 AE2 原版接口和 ExtendedAE 扩展接口
|
||||
InterfaceMenu menu = null;
|
||||
com.glodblock.github.extendedae.container.ContainerExInterface exMenu = null;
|
||||
if (player.containerMenu instanceof InterfaceMenu im) {
|
||||
menu = im;
|
||||
} else if (player.containerMenu instanceof com.glodblock.github.extendedae.container.ContainerExInterface cem) {
|
||||
exMenu = cem;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var logic = (menu != null ? menu.getHost() : exMenu.getHost()).getInterfaceLogic();
|
||||
var config = logic.getConfig();
|
||||
if (msg.slotIndex == -1) {
|
||||
// 对所有配置槽统一生效(不依赖槽位语义,直接按 config.size() 遍历)
|
||||
int size = config.size();
|
||||
for (int idx = 0; idx < size; idx++) {
|
||||
var st = config.getStack(idx);
|
||||
if (st == null) continue;
|
||||
|
||||
long current = st.amount();
|
||||
int factor = Math.max(1, msg.factor);
|
||||
long next;
|
||||
if (msg.divide) {
|
||||
if (factor <= 1) continue;
|
||||
if (current % factor != 0) continue;
|
||||
next = current / factor;
|
||||
if (next < 1) next = 1;
|
||||
} else {
|
||||
if (factor <= 1) continue;
|
||||
next = current * factor;
|
||||
if (next < 1) next = 1;
|
||||
}
|
||||
|
||||
GenericStack newStack = new GenericStack(st.what(), next);
|
||||
config.setStack(idx, newStack);
|
||||
}
|
||||
} else {
|
||||
var stack = config.getStack(msg.slotIndex);
|
||||
if (stack == null) return; // 槽位无标记
|
||||
|
||||
long current = stack.amount();
|
||||
int factor = Math.max(1, msg.factor);
|
||||
long next;
|
||||
if (msg.divide) {
|
||||
// 只能整除,且至少为 1
|
||||
if (factor <= 1) return;
|
||||
if (current % factor != 0) return; // 不能整除则跳过
|
||||
next = current / factor;
|
||||
if (next < 1) next = 1;
|
||||
} else {
|
||||
// 倍增,至少为 1
|
||||
if (factor <= 1) return;
|
||||
next = current * factor;
|
||||
if (next < 1) next = 1;
|
||||
}
|
||||
|
||||
// 应用
|
||||
GenericStack newStack = new GenericStack(stack.what(), next);
|
||||
config.setStack(msg.slotIndex, newStack);
|
||||
}
|
||||
// 不需要显式保存:InterfaceLogic.config 的变更监听器会触发 host.saveChanges() 与计划更新
|
||||
} catch (Throwable ignored) {}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.extendedae_plus.network;
|
|||
import appeng.helpers.patternprovider.PatternContainer;
|
||||
import appeng.menu.implementations.PatternAccessTermMenu;
|
||||
import appeng.menu.me.items.PatternEncodingTermMenu;
|
||||
import com.extendedae_plus.init.ModNetwork;
|
||||
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
||||
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -27,17 +31,32 @@ public class ToggleAdvancedBlockingC2SPacket {
|
|||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
||||
|
||||
// 通过 accessor 获取逻辑与当前状态
|
||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof AdvancedBlockingHolder holder) {
|
||||
boolean current = holder.eap$getAdvancedBlocking();
|
||||
boolean next = !current;
|
||||
holder.eap$setAdvancedBlocking(next);
|
||||
// 保存并触发 AE2 的菜单 @GuiSync 广播到所有观看该菜单的玩家
|
||||
logic.saveChanges();
|
||||
var containerMenu = player.containerMenu;
|
||||
if (containerMenu instanceof PatternProviderMenu menu) {
|
||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof AdvancedBlockingHolder holder) {
|
||||
boolean current = holder.eap$getAdvancedBlocking();
|
||||
boolean next = !current;
|
||||
holder.eap$setAdvancedBlocking(next);
|
||||
// 自动开启原版阻挡
|
||||
logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
|
||||
// 保存并触发 AE2 的菜单 @GuiSync 广播到所有观看该菜单的玩家
|
||||
logic.saveChanges();
|
||||
}
|
||||
}else if (containerMenu instanceof AdvPatternProviderMenu menu){
|
||||
var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof AdvancedBlockingHolder holder) {
|
||||
boolean current = holder.eap$getAdvancedBlocking();
|
||||
boolean next = !current;
|
||||
holder.eap$setAdvancedBlocking(next);
|
||||
// 自动开启原版阻挡
|
||||
logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
|
||||
// 保存并触发 AE2 的菜单 @GuiSync 广播到所有观看该菜单的玩家
|
||||
logic.saveChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S: Toggle the accelerateEnabled flag on the EntitySpeedTickerPart bound to the open menu.
|
||||
*/
|
||||
public class ToggleEntityTickerC2SPacket {
|
||||
public ToggleEntityTickerC2SPacket() {
|
||||
}
|
||||
|
||||
public static void encode(ToggleEntityTickerC2SPacket msg, FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
public static ToggleEntityTickerC2SPacket decode(FriendlyByteBuf buf) {
|
||||
return new ToggleEntityTickerC2SPacket();
|
||||
}
|
||||
|
||||
public static void handle(ToggleEntityTickerC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof EntitySpeedTickerMenu menu)) return;
|
||||
|
||||
EntitySpeedTickerPart part = menu.getHost();
|
||||
if (part == null) return;
|
||||
|
||||
// 切换部件上的状态,并把新状态同步到菜单字段,随后广播以通知客户端
|
||||
boolean current = part.getAccelerateEnabled();
|
||||
boolean next = !current;
|
||||
part.setAccelerateEnabled(next);
|
||||
// 确保菜单上的字段也被更新,这样 @GuiSync 会把状态发回客户端
|
||||
menu.setAccelerateEnabled(next);
|
||||
menu.broadcastChanges();
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,12 @@ package com.extendedae_plus.network;
|
|||
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -27,15 +29,25 @@ public class ToggleSmartDoublingC2SPacket {
|
|||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
||||
|
||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof SmartDoublingHolder holder) {
|
||||
boolean current = holder.eap$getSmartDoubling();
|
||||
boolean next = !current;
|
||||
holder.eap$setSmartDoubling(next);
|
||||
logic.saveChanges();
|
||||
var containerMenu = player.containerMenu;
|
||||
if (containerMenu instanceof PatternProviderMenu menu) {
|
||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof SmartDoublingHolder holder) {
|
||||
boolean current = holder.eap$getSmartDoubling();
|
||||
boolean next = !current;
|
||||
holder.eap$setSmartDoubling(next);
|
||||
logic.saveChanges();
|
||||
}
|
||||
}else if (containerMenu instanceof AdvPatternProviderMenu menu){
|
||||
var accessor = (AdvPatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof SmartDoublingHolder holder) {
|
||||
boolean current = holder.eap$getSmartDoubling();
|
||||
boolean next = !current;
|
||||
holder.eap$setSmartDoubling(next);
|
||||
logic.saveChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
|
|
|
|||
158
src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java
Normal file
158
src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
/**
|
||||
* 配置解析工具类:用于解析黑名单与倍率配置的字符串
|
||||
*/
|
||||
public final class ConfigParsingUtils {
|
||||
private ConfigParsingUtils() {}
|
||||
|
||||
public static final class MultiplierEntry {
|
||||
public final Pattern pattern;
|
||||
public final double multiplier;
|
||||
|
||||
public MultiplierEntry(Pattern pattern, double multiplier) {
|
||||
this.pattern = pattern;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译用户提供的匹配串。支持简单的 glob 语法('*' 和 '?')以及完整的正则表达式。
|
||||
* 规则:
|
||||
* - 如果字符串包含 ".*" 或其他正则元字符,优先尝试按正则编译(若失败则回退到 glob 转换)。
|
||||
* - 否则若包含 '*' 或 '?',将按 glob 语法转换为正则。
|
||||
* - 否则按字面量匹配处理。
|
||||
*/
|
||||
public static Pattern compilePattern(String raw) {
|
||||
if (raw == null) throw new IllegalArgumentException("pattern is null");
|
||||
raw = raw.trim();
|
||||
if (raw.isEmpty()) throw new IllegalArgumentException("pattern is empty");
|
||||
|
||||
// If it looks like regex (contains '.*' or regex metachar), try regex first
|
||||
if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) {
|
||||
try {
|
||||
return Pattern.compile("^" + raw + "$");
|
||||
} catch (PatternSyntaxException ignored) {
|
||||
// fallback to glob below
|
||||
}
|
||||
}
|
||||
|
||||
// If contains glob chars, convert to regex
|
||||
if (raw.contains("*") || raw.contains("?")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('^');
|
||||
for (char c : raw.toCharArray()) {
|
||||
switch (c) {
|
||||
case '*': sb.append(".*"); break;
|
||||
case '?': sb.append('.'); break;
|
||||
default:
|
||||
// escape regex special chars
|
||||
if (".\\+[]{}()^$|".indexOf(c) >= 0) {
|
||||
sb.append('\\');
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
sb.append('$');
|
||||
return Pattern.compile(sb.toString());
|
||||
}
|
||||
|
||||
// Otherwise treat as a literal (match exact)
|
||||
return Pattern.compile("^" + Pattern.quote(raw) + "$");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse multiplier entries like 'modid:block 2x' into MultiplierEntry objects.
|
||||
* Accepts values with optional trailing 'x' (case-insensitive).
|
||||
*/
|
||||
public static MultiplierEntry parseMultiplierEntry(String entry) {
|
||||
if (entry == null) return null;
|
||||
String[] parts = entry.trim().split("\\s+");
|
||||
if (parts.length < 2) return null;
|
||||
String key = parts[0];
|
||||
String val = parts[1].toLowerCase();
|
||||
if (val.endsWith("x")) val = val.substring(0, val.length() - 1);
|
||||
double m;
|
||||
try {
|
||||
m = Double.parseDouble(val);
|
||||
} catch (NumberFormatException ex) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Pattern p = compilePattern(key);
|
||||
return new MultiplierEntry(p, m);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Pattern> compilePatterns(List<? extends String> raw) {
|
||||
List<Pattern> out = new ArrayList<>();
|
||||
if (raw == null) return out;
|
||||
for (String s : raw) {
|
||||
if (s == null || s.isBlank()) continue;
|
||||
try { out.add(compilePattern(s)); } catch (IllegalArgumentException ignored) {}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static List<MultiplierEntry> parseMultiplierList(List<? extends String> raw) {
|
||||
List<MultiplierEntry> out = new ArrayList<>();
|
||||
if (raw == null) return out;
|
||||
for (String s : raw) {
|
||||
MultiplierEntry me = parseMultiplierEntry(s);
|
||||
if (me != null) out.add(me);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ------------------ 全局缓存与接口 ------------------
|
||||
private static volatile List<Pattern> cachedBlacklist = null;
|
||||
private static volatile List<MultiplierEntry> cachedMultiplierEntries = null;
|
||||
private static final Object CACHE_LOCK = new Object();
|
||||
|
||||
/**
|
||||
* 获取已解析并缓存的黑名单(线程安全、懒加载)。
|
||||
*/
|
||||
public static List<Pattern> getCachedBlacklist(java.util.List<? extends String> source) {
|
||||
if (cachedBlacklist == null) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedBlacklist == null) {
|
||||
cachedBlacklist = compilePatterns(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
return java.util.List.copyOf(cachedBlacklist);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已解析并缓存的倍率列表(线程安全、懒加载)。
|
||||
*/
|
||||
public static List<MultiplierEntry> getCachedMultiplierEntries(java.util.List<? extends String> source) {
|
||||
if (cachedMultiplierEntries == null) {
|
||||
synchronized (CACHE_LOCK) {
|
||||
if (cachedMultiplierEntries == null) {
|
||||
cachedMultiplierEntries = parseMultiplierList(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
return java.util.List.copyOf(cachedMultiplierEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存,下一次获取时将重新从提供的源解析(或调用方可以重新调用 getter)。
|
||||
*/
|
||||
public static void reload() {
|
||||
synchronized (CACHE_LOCK) {
|
||||
cachedBlacklist = null;
|
||||
cachedMultiplierEntries = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* 数字格式化工具类,提供大数字和小数的格式化功能
|
||||
*/
|
||||
|
|
@ -7,46 +9,59 @@ public class NumberFormatUtil {
|
|||
private NumberFormatUtil() {throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");}
|
||||
|
||||
/**
|
||||
* 格式化数字,将大数字转换为k、m等格式
|
||||
* 支持小数显示,小数点后为0则不显示0
|
||||
* 格式化数字,将大数字转换为 k、M、G 等格式(物品用整数/一位小数显示规则)
|
||||
* @param number 要格式化的数字
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String formatNumber(long number) {
|
||||
if (number < 1000) {
|
||||
return String.valueOf(number);
|
||||
} else if (number < 1000000) {
|
||||
double value = number / 1000.0;
|
||||
return formatDecimal(value, "k");
|
||||
} else {
|
||||
double value = number / 1000000.0;
|
||||
return formatDecimal(value, "m");
|
||||
}
|
||||
|
||||
String[] preFixes = new String[]{"k", "M", "G", "T", "P", "E", "Z", "Y"};
|
||||
double value = number;
|
||||
String level = "";
|
||||
|
||||
for (int offset = 0; value >= 1000.0 && offset < preFixes.length; ++offset) {
|
||||
value /= 1000.0;
|
||||
level = preFixes[offset];
|
||||
}
|
||||
|
||||
return formatDecimal(value, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化带小数的数字,支持流体等需要显示小数的场景
|
||||
* 格式化带小数的数字,支持流体等需要显示小数的场景(保留至多两位小数)
|
||||
* 流体显示规则不同于物品,统一使用两位小数的 DecimalFormat
|
||||
* @param value 小数值
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public static String formatNumberWithDecimal(double value) {
|
||||
if (value < 1000) {
|
||||
DecimalFormat smallDf = new DecimalFormat("#.##");
|
||||
// 小于1000时,若是整数则显示整数,否则显示最多两位小数
|
||||
if (value == (long) value) {
|
||||
return String.valueOf((long) value);
|
||||
} else {
|
||||
return String.format("%.1f", value).replaceAll("\\.0$", "");
|
||||
return smallDf.format(value);
|
||||
}
|
||||
} else if (value < 1000000) {
|
||||
return formatDecimal(value / 1000.0, "k");
|
||||
} else {
|
||||
return formatDecimal(value / 1000000.0, "m");
|
||||
}
|
||||
|
||||
String[] preFixes = new String[]{"k", "M", "G", "T", "P", "E", "Z", "Y"};
|
||||
String level = "";
|
||||
for (int offset = 0; value >= 1000.0 && offset < preFixes.length; ++offset) {
|
||||
value /= 1000.0;
|
||||
level = preFixes[offset];
|
||||
}
|
||||
|
||||
DecimalFormat df = new DecimalFormat("#.##");
|
||||
return df.format(value) + level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化小数,如果小数点后为0则不显示0
|
||||
* 格式化小数(物品显示规则):如果小数点后接近 0 则显示为整数,否则显示一位小数
|
||||
* @param value 小数值
|
||||
* @param suffix 后缀(k、m、g等)
|
||||
* @param suffix 后缀(k、M、G 等)
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
private static String formatDecimal(double value, String suffix) {
|
||||
|
|
@ -54,9 +69,9 @@ public class NumberFormatUtil {
|
|||
if (Math.abs(value - Math.round(value)) < 0.001) {
|
||||
return String.valueOf(Math.round(value)) + suffix;
|
||||
} else {
|
||||
// 修复重复后缀问题:先格式化数字,再添加后缀
|
||||
// 使用一位小数显示,去掉末尾 .0
|
||||
String formatted = String.format("%.1f", value).replaceAll("\\.0$", "");
|
||||
return formatted + suffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import appeng.api.stacks.AEKey;
|
|||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
|
||||
public final class PatternScaler {
|
||||
|
|
@ -56,7 +56,7 @@ public final class PatternScaler {
|
|||
}
|
||||
// 小请求绕过:若请求量小且不会带来收益,则不启用缩放(返回 null)
|
||||
try {
|
||||
int minBenefit = ModConfigs.SMART_SCALING_MIN_BENEFIT_FACTOR.get();
|
||||
int minBenefit = ModConfig.INSTANCE.smartScalingMinBenefitFactor;
|
||||
if (minBenefit > 1 && requestedAmount > 0 && requestedAmount < perOperationTarget * (long) minBenefit) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ public final class PatternScaler {
|
|||
}
|
||||
// 应用配置的最大倍数上限(0 表示不限制)
|
||||
try {
|
||||
int maxMul = ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get();
|
||||
int maxMul = ModConfig.INSTANCE.smartScalingMaxMultiplier;
|
||||
if (maxMul > 0 && multiplier > maxMul) {
|
||||
multiplier = maxMul;
|
||||
}
|
||||
|
|
|
|||
161
src/main/java/com/extendedae_plus/util/PowerUtils.java
Normal file
161
src/main/java/com/extendedae_plus/util/PowerUtils.java
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
|
||||
/**
|
||||
* 用于计算实体加速器的能耗与加速倍率的工具类
|
||||
*/
|
||||
public final class PowerUtils {
|
||||
private PowerUtils() {}
|
||||
// ---- 重构后的 API ----
|
||||
/**
|
||||
* 将 card multipliers(按插槽序)计算乘积并应用 cap 规则(见 capForHighestMultiplier)
|
||||
* @param multipliers iterable of per-card multipliers
|
||||
* @param maxCards 最多计入的卡数
|
||||
* @return 被 cap 约束后的乘积
|
||||
*/
|
||||
public static long computeProductWithCap(Iterable<Integer> multipliers, int maxCards) {
|
||||
long product = 1L;
|
||||
int considered = 0;
|
||||
int highest = 1;
|
||||
for (Integer m : multipliers) {
|
||||
if (m == null) continue;
|
||||
if (considered >= maxCards) break;
|
||||
int mult = m.intValue();
|
||||
if (mult <= 0) mult = 1;
|
||||
product *= mult;
|
||||
highest = Math.max(highest, mult);
|
||||
considered++;
|
||||
}
|
||||
long cap = capForHighestMultiplier(highest);
|
||||
return Math.min(product, cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据最高单卡 multiplier 返回 cap 值
|
||||
*/
|
||||
public static long capForHighestMultiplier(int highestMultiplier) {
|
||||
if (highestMultiplier >= 16) return 1024L;
|
||||
if (highestMultiplier >= 8) return 256L;
|
||||
if (highestMultiplier >= 4) return 64L;
|
||||
if (highestMultiplier >= 2) return 8L;
|
||||
return 1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从菜单对象读取前 maxCards 个加速卡的 multiplier 并计算 product with cap
|
||||
*/
|
||||
public static long computeProductWithCapFromMenu(appeng.menu.implementations.UpgradeableMenu<?> menu, int maxCards) {
|
||||
java.util.List<Integer> list = new java.util.ArrayList<>();
|
||||
int considered = 0;
|
||||
for (var stack : menu.getUpgrades()) {
|
||||
if (considered >= maxCards) break;
|
||||
if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
|
||||
int multVal = EntitySpeedCardItem.readMultiplier(stack);
|
||||
int count = Math.min(stack.getCount(), maxCards - considered);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(multVal);
|
||||
considered++;
|
||||
if (considered >= maxCards) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return computeProductWithCap(list, maxCards);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一组 ItemStack(升级槽)直接计算 product with cap(最多 maxCards)
|
||||
*/
|
||||
public static long computeProductWithCapFromStacks(Iterable<net.minecraft.world.item.ItemStack> stacks, int maxCards) {
|
||||
java.util.List<Integer> list = new java.util.ArrayList<>();
|
||||
int considered = 0;
|
||||
for (var stack : stacks) {
|
||||
if (considered >= maxCards) break;
|
||||
if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
|
||||
int multVal = EntitySpeedCardItem.readMultiplier(stack);
|
||||
int count = Math.min(stack.getCount(), maxCards - considered);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(multVal);
|
||||
considered++;
|
||||
if (considered >= maxCards) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return computeProductWithCap(list, maxCards);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最终消耗:把 product 转换为等效卡数(log2)并调用 getFinalPower
|
||||
*/
|
||||
public static double computeFinalPowerForProduct(long product, int energyCardCount) {
|
||||
if (product <= 1L) return 0.0;
|
||||
double base = ModConfig.INSTANCE.entityTickerCost;
|
||||
|
||||
// 计算以2为底的对数(用于分档与公式)
|
||||
double log2 = Math.log(product) / Math.log(2.0);
|
||||
|
||||
// 分档:product==2 为一档;4..256 为中档;512..1024 为高档
|
||||
double raw;
|
||||
if (product == 2L) {
|
||||
// 轻量档:线性小幅增长
|
||||
raw = base * 4;
|
||||
} else if (product <= 256L) {
|
||||
// 中档:增长放缓(使用 1.5 * log2)
|
||||
raw = base * Math.pow(2.0, 1.5 * log2) * 2;
|
||||
} else {
|
||||
// 高档:增长较快(使用 2.5 * log2)
|
||||
raw = base * Math.pow(2.0, 2.5 * log2);
|
||||
}
|
||||
|
||||
double reduction = getReductionPercent(energyCardCount);
|
||||
return raw * (1.0 - reduction) / 8.0;
|
||||
}
|
||||
|
||||
/* ----------------- legacy helpers (restored) ----------------- */
|
||||
public static double getGrowthFactor(int speedCardCount) {
|
||||
if (speedCardCount <= 0) return 1.0;
|
||||
if (speedCardCount == 1) return 2.0;
|
||||
if (speedCardCount <= 6) return Math.pow(2.0, 2.0 * speedCardCount);
|
||||
return Math.pow(2.0, 3.0 * speedCardCount);
|
||||
}
|
||||
|
||||
public static double getRawPower(int speedCardCount) {
|
||||
double base = ModConfig.INSTANCE.entityTickerCost;
|
||||
return base * getGrowthFactor(speedCardCount);
|
||||
}
|
||||
|
||||
public static double getReductionPercent(int energyCardCount) {
|
||||
if (energyCardCount <= 0) return 0.0;
|
||||
if (energyCardCount == 1) return 0.1;
|
||||
if (energyCardCount >= 8) return 0.5;
|
||||
return 0.5 * (1.0 - Math.pow(0.7, energyCardCount));
|
||||
}
|
||||
|
||||
public static double getFinalPower(int speedCardCount, int energyCardCount) {
|
||||
double raw = getRawPower(speedCardCount);
|
||||
double reduction = getReductionPercent(energyCardCount);
|
||||
return raw * (1.0 - reduction) / 4.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回能源卡减免后剩余的功耗比率(例如 1 张能源卡 -> 0.9)
|
||||
* @param energyCardCount 能源卡数量
|
||||
* @return 剩余功耗比率
|
||||
*/
|
||||
public static double getRemainingRatio(int energyCardCount) {
|
||||
return 1.0 - getReductionPercent(energyCardCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将剩余功耗比率格式化为百分比字符串(例如 0.9 -> "90%")
|
||||
*/
|
||||
public static String formatPercentage(double ratio) {
|
||||
double pct = ratio * 100.0;
|
||||
// 如果为整数则无小数
|
||||
if (Math.abs(pct - Math.round(pct)) < 1e-9) {
|
||||
return String.format("%d%%", Math.round(pct));
|
||||
}
|
||||
return String.format("%.2f%%", pct);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.extendedae_plus.util.storage;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
/**
|
||||
* InfinityDataStorage
|
||||
*
|
||||
* 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。
|
||||
* 数据结构说明:
|
||||
* - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目
|
||||
* - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示:
|
||||
* - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值
|
||||
* - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式
|
||||
*
|
||||
* 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。
|
||||
*/
|
||||
public class InfinityDataStorage {
|
||||
|
||||
/** 空实例(表示没有数据) */
|
||||
public static final InfinityDataStorage EMPTY = new InfinityDataStorage();
|
||||
|
||||
/** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */
|
||||
public ListTag keys;
|
||||
/**
|
||||
* 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag):
|
||||
* - 若数量能放入 long,则 CompoundTag 包含键 "l"(long)
|
||||
* - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式
|
||||
*/
|
||||
public ListTag amounts;
|
||||
|
||||
public InfinityDataStorage() {
|
||||
this(new ListTag(), new ListTag());
|
||||
}
|
||||
|
||||
private InfinityDataStorage(ListTag keys, ListTag amounts) {
|
||||
this.keys = keys;
|
||||
this.amounts = amounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前数据封装为 CompoundTag 以写入存档
|
||||
*/
|
||||
public CompoundTag serializeNBT() {
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
nbt.put("keys", keys);
|
||||
nbt.put("amounts", amounts);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从存档读取数据并构造实例
|
||||
*/
|
||||
public static InfinityDataStorage loadFromNBT(CompoundTag nbt) {
|
||||
ListTag stackKeys = nbt.getList("keys", Tag.TAG_COMPOUND);
|
||||
// amounts 以 CompoundTag 列表存储,每个 CompoundTag 内含 long 或 String
|
||||
ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND);
|
||||
return new InfinityDataStorage(stackKeys, stackAmounts);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.extendedae_plus.util.storage;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* InfinityStorageManager
|
||||
* <p>
|
||||
* 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
|
||||
* 功能要点:
|
||||
* - 在世界加载时从存档恢复所有 cell 的数据
|
||||
* - 提供按 UUID 获取/创建单个 cell 的数据容器
|
||||
* - 在世界保存时将内存数据打包为 NBT 写回存档
|
||||
*/
|
||||
public class InfinityStorageManager extends SavedData {
|
||||
|
||||
/**
|
||||
* SavedData 文件名常量
|
||||
*/
|
||||
public static final String FILE_NAME = "eap_infinity_biginteger_cells";
|
||||
/**
|
||||
* 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充)
|
||||
*/
|
||||
public static InfinityStorageManager INSTANCE = null;
|
||||
/**
|
||||
* UUID -> 数据 的内存映射
|
||||
*/
|
||||
private final Map<UUID, InfinityDataStorage> cells = new HashMap<>();
|
||||
|
||||
public InfinityStorageManager() {
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 NBT 构造:用于在世界加载时从存档恢复数据
|
||||
*/
|
||||
public InfinityStorageManager(CompoundTag nbt) {
|
||||
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
|
||||
for (int i = 0; i < cellList.size(); i++) {
|
||||
CompoundTag cell = cellList.getCompound(i);
|
||||
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data")));
|
||||
}
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
|
||||
*/
|
||||
public static InfinityStorageManager getForLevel(ServerLevel level) {
|
||||
if (INSTANCE == null && level != null) {
|
||||
INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompoundTag save(@NotNull CompoundTag nbt) {
|
||||
// 将内存中的所有 cell 序列化为一个 ListTag
|
||||
ListTag cellList = new ListTag();
|
||||
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
|
||||
CompoundTag cell = new CompoundTag();
|
||||
cell.putUUID("uuid", entry.getKey());
|
||||
cell.put("data", entry.getValue().serializeNBT());
|
||||
cellList.add(cell);
|
||||
}
|
||||
nbt.put("list", cellList);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
|
||||
*/
|
||||
public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
|
||||
cells.put(uuid, infinityDataStorage);
|
||||
setDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建某个 UUID 对应的数据容器
|
||||
*/
|
||||
public InfinityDataStorage getOrCreateCell(UUID uuid) {
|
||||
if (!cells.containsKey(uuid)) {
|
||||
updateCell(uuid, new InfinityDataStorage());
|
||||
}
|
||||
return cells.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表)
|
||||
*/
|
||||
public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) {
|
||||
InfinityDataStorage cellToModify = getOrCreateCell(cellID);
|
||||
if (stackKeys != null && stackAmounts != null) {
|
||||
cellToModify.keys = stackKeys;
|
||||
cellToModify.amounts = stackAmounts;
|
||||
}
|
||||
updateCell(cellID, cellToModify);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除某个 UUID 的持久化记录并标记为脏
|
||||
*/
|
||||
public void removeCell(UUID uuid) {
|
||||
cells.remove(uuid);
|
||||
setDirty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.extendedae_plus.wireless;
|
||||
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
|
@ -63,7 +63,7 @@ public final class WirelessMasterRegistry {
|
|||
}
|
||||
|
||||
private static boolean useGlobal() {
|
||||
return ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get();
|
||||
return ModConfig.INSTANCE.wirelessCrossDimEnable;
|
||||
}
|
||||
|
||||
private record Key(ResourceKey<Level> dim, long freq) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.extendedae_plus.wireless;
|
|||
import appeng.api.networking.GridHelper;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.me.service.helpers.ConnectionWrapper;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.config.ModConfig;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
|
@ -64,12 +64,12 @@ public class WirelessSlaveLink {
|
|||
shutdown = false;
|
||||
distance = 0.0D;
|
||||
|
||||
boolean crossDim = ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get();
|
||||
boolean crossDim = ModConfig.INSTANCE.wirelessCrossDimEnable;
|
||||
if (master != null && !master.isEndpointRemoved() && (crossDim || master.getServerLevel() == level)) {
|
||||
if (!crossDim) {
|
||||
distance = Math.sqrt(master.getBlockPos().distSqr(host.getBlockPos()));
|
||||
}
|
||||
double maxRange = ModConfigs.WIRELESS_MAX_RANGE.get();
|
||||
double maxRange = ModConfig.INSTANCE.wirelessMaxRange;
|
||||
if (crossDim || distance <= maxRange) {
|
||||
// 保持/建立连接
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"$schema": "schema.json",
|
||||
"includes": [
|
||||
"common/common.json",
|
||||
"common/player_inventory.json"
|
||||
],
|
||||
"background": {
|
||||
"texture": "guis/blank_background.png",
|
||||
"srcRect": [
|
||||
0,
|
||||
0,
|
||||
176,
|
||||
205
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"dialog_title": {
|
||||
"text": {
|
||||
"translate": "item.extendedae_plus.entity_speed_ticker"
|
||||
},
|
||||
"position": {
|
||||
"left": 8,
|
||||
"top": 6
|
||||
}
|
||||
},
|
||||
"enable": {
|
||||
"position": {
|
||||
"top": 20,
|
||||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
},
|
||||
"speed": {
|
||||
"position": {
|
||||
"top": 35,
|
||||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
},
|
||||
"energy": {
|
||||
"position": {
|
||||
"top": 50,
|
||||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
},
|
||||
"power_ratio": {
|
||||
"position": {
|
||||
"top": 65,
|
||||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
}
|
||||
,
|
||||
"multiplier": {
|
||||
"position": {
|
||||
"top": 80,
|
||||
"left": 88
|
||||
},
|
||||
"align": "CENTER"
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"toolbox": {
|
||||
"right": -2,
|
||||
"bottom": 50,
|
||||
"width": 68,
|
||||
"height": 68
|
||||
}
|
||||
},
|
||||
"slots": {
|
||||
"TOOLBOX": {
|
||||
"bottom": 42,
|
||||
"right": -10,
|
||||
"grid": "BREAK_AFTER_3COLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/main/resources/assets/ae2/textures/guis/blank_background.png
Normal file
BIN
src/main/resources/assets/ae2/textures/guis/blank_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -3,18 +3,60 @@
|
|||
"gui.expatternprovider.hide_slots": "Hide Slots",
|
||||
"gui.expatternprovider.show_slots": "Show Slots",
|
||||
"gui.expatternprovider.clear_selection": "Clear Selection",
|
||||
"gui.extendedae_plus.pattern_provider.select": "Select Pattern Provider",
|
||||
"gui.extendedae_plus.pattern_provider.selected": "Pattern Provider Selected",
|
||||
"gui.extendedae_plus.pattern_provider.unselected": "Click to select this pattern provider",
|
||||
"message.extendedae_plus.no_provider_selected": "Please select a pattern provider first",
|
||||
"message.extendedae_plus.invalid_pattern": "Please hold a valid encoded pattern",
|
||||
"message.extendedae_plus.upload_success": "Pattern uploaded successfully",
|
||||
"message.extendedae_plus.upload_failed": "Failed to upload pattern - no empty slots",
|
||||
"message.extendedae_plus.uploading_pattern": "Uploading pattern...",
|
||||
"gui.extendedae_plus.group_header.choice": "Toggle Group Choice",
|
||||
"gui.extendedae_plus.group_header.choiceable": "Group is choiceable",
|
||||
"gui.extendedae_plus.group_header.not_choiceable": "Group is not choiceable",
|
||||
"gui.extendedae_plus.global.toggle_blocking": "Toggle Blocking Mode",
|
||||
"gui.extendedae_plus.global.toggle_adv_blocking": "Toggle Advanced Blocking",
|
||||
"gui.extendedae_plus.global.toggle_smart_doubling": "Toggle Smart Doubling",
|
||||
"gui.extendedae_plus.global.all_on": "All On",
|
||||
"gui.extendedae_plus.global.all_off": "All Off",
|
||||
|
||||
"itemGroup.extendedae_plus.main": "ExtendedAE Plus",
|
||||
"item.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
|
||||
"item.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
|
||||
"item.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
|
||||
"item.extendedae_plus.64x_crafting_accelerator": "64x Crafting Accelerator",
|
||||
"item.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
|
||||
"item.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
|
||||
"item.extendedae_plus.network_pattern_controller": "Pattern Supplier State Controller",
|
||||
"item.extendedae_plus.entity_speed_card": "Entity Acceleration Card",
|
||||
"item.extendedae_plus.entity_speed_card.x2": "Entity Acceleration Card (x2)",
|
||||
"item.extendedae_plus.entity_speed_card.x4": "Entity Acceleration Card (x4)",
|
||||
"item.extendedae_plus.entity_speed_card.x8": "Entity Acceleration Card (x8)",
|
||||
"item.extendedae_plus.entity_speed_card.x16": "Entity Acceleration Card (x16)",
|
||||
"item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence",
|
||||
"tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6Through ninefold sacrifice, the Void echoes§r—§8Iava, Lord of the Void§r, bestows upon thee this artifact",
|
||||
"tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b—§4A §dUni§cverse §eWith§ain §6A §bSin§5gle §9Point",
|
||||
"tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s",
|
||||
"tooltip.extendedae_plus.entity_speed_card.max": "Max effective: %s x",
|
||||
|
||||
"block.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
|
||||
"block.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
|
||||
"block.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
|
||||
"block.extendedae_plus.64x_crafting_accelerator": "64x Crafting Accelerator",
|
||||
"block.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
|
||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
|
||||
"block.extendedae_plus.network_pattern_controller": "Pattern Supplier State Controller",
|
||||
|
||||
"extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix",
|
||||
"extendedae_plus.upload_to_matrix.success": "Pattern uploaded to the assembly matrix",
|
||||
"extendedae_plus.upload_to_matrix.fail_not_crafting": "Only crafting patterns are supported; processing patterns will be ignored",
|
||||
"extendedae_plus.upload_to_matrix.fail_no_matrix": "No formed assembly matrix found in the current network",
|
||||
"extendedae_plus.upload_to_matrix.fail_full": "The assembly matrix pattern inventory is full or cannot insert",
|
||||
"extendedae_plus.screen.reload_mapping": "Reload Mapping",
|
||||
"extendedae_plus.screen.add_mapping": "Add Mapping",
|
||||
"extendedae_plus.screen.cn_name": "Chinese Name",
|
||||
"extendedae_plus.button.choose_provider": "Upload Pattern",
|
||||
"extendedae_plus.pattern.hovertext.player": "Encoded by %s",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker": "Entity Speed Ticker",
|
||||
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§lThe machine has been disabled",
|
||||
"screen.extendedae_plus.entity_speed_ticker.energy": "Energy Usage: %s FE/t",
|
||||
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "Power ratio: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.speed": "Current speed multiplier: %d",
|
||||
"screen.extendedae_plus.entity_speed_ticker.multiplier": "Extra consumption multiplier: %s",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "Requires Entity Acceleration Card(s) to enable acceleration",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.max": "Maximum up to 1024x speed",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.energy": "Acceleration consumes AE network power; insufficient power disables acceleration",
|
||||
|
||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Info",
|
||||
"config.jade.plugin_extendedae_plus.wt_frequency": "Show Frequency",
|
||||
|
|
@ -24,18 +66,24 @@
|
|||
"config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State",
|
||||
"extendedae_plus.tooltip.frequency": "Frequency: %d",
|
||||
"extendedae_plus.tooltip.master_mode": "Mode: %s",
|
||||
"extendedae_plus.tooltip.locked": "Locked: %s"
|
||||
,
|
||||
"screen.extendedae_plus.title": "ExtendedAE Plus Config",
|
||||
"config.extendedae_plus.pageMultiplier": "Pattern Provider Page Multiplier",
|
||||
"config.extendedae_plus.pageMultiplier_with_range": "Pattern Provider Page Multiplier (1-64)",
|
||||
"config.extendedae_plus.wirelessMaxRange": "Wireless Max Range",
|
||||
"config.extendedae_plus.wirelessMaxRange_with_range": "Wireless Max Range (1-4096)",
|
||||
"config.extendedae_plus.wirelessCrossDimEnable": "Allow Wireless Cross-Dimension",
|
||||
"config.extendedae_plus.providerRoundRobinEnable": "Enable Provider Round-Robin (Smart Doubling)",
|
||||
"config.extendedae_plus.state_on": "On",
|
||||
"config.extendedae_plus.state_off": "Off",
|
||||
"config.extendedae_plus.smartScalingMaxMultiplier": "Smart Scaling Max Multiplier",
|
||||
"config.extendedae_plus.smartScalingMaxMultiplier_with_range": "Smart Scaling Max Multiplier (0 = Unlimited)"
|
||||
,"config.extendedae_plus.smartScalingMinBenefitFactor": "Smart Scaling Min Benefit Factor (default 4)"
|
||||
"extendedae_plus.tooltip.locked": "Locked: %s",
|
||||
|
||||
"config.screen.extendedae_plus": "ExtendedAE Plus Config",
|
||||
"config.extendedae_plus.option.pageMultiplier": "Pattern Provider Page Multiplier",
|
||||
"config.extendedae_plus.option.pageMultiplier_with_range": "Pattern Provider Page Multiplier",
|
||||
"config.extendedae_plus.option.wirelessMaxRange": "Wireless Max Range",
|
||||
"config.extendedae_plus.option.wirelessMaxRange_with_range": "Wireless Max Range",
|
||||
"config.extendedae_plus.option.wirelessCrossDimEnable": "Allow Wireless Cross-Dimension",
|
||||
"config.extendedae_plus.option.providerRoundRobinEnable": "Enable Provider Round-Robin (Smart Doubling)",
|
||||
"config.extendedae_plus.option.state_on": "On",
|
||||
"config.extendedae_plus.option.state_off": "Off",
|
||||
"config.extendedae_plus.option.showEncoderPatternPlayer": "Show Encoded Pattern Player",
|
||||
"config.extendedae_plus.option.patternTerminalShowSlotsDefault": "Pattern Terminal Show Slots By Default",
|
||||
"config.extendedae_plus.option.smartScalingMaxMultiplier": "Smart Scaling Max Multiplier",
|
||||
"config.extendedae_plus.option.smartScalingMaxMultiplier_with_range": "Smart Scaling Max Multiplier",
|
||||
"config.extendedae_plus.option.smartScalingMinBenefitFactor": "Smart Scaling Min Benefit Factor",
|
||||
"config.extendedae_plus.option.entityTickerCost": "Entity Ticker Base Energy Cost",
|
||||
"config.extendedae_plus.option.entityTickerBlackList": "Entity Ticker Blacklist",
|
||||
"config.extendedae_plus.option.entityTickerMultipliers": "Entity Ticker Extra Consumption Multipliers",
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold"
|
||||
}
|
||||
|
|
@ -3,9 +3,39 @@
|
|||
"gui.expatternprovider.hide_slots": "隐藏槽位",
|
||||
"gui.expatternprovider.show_slots": "显示槽位",
|
||||
"gui.expatternprovider.clear_selection": "取消选择",
|
||||
"block.extendedae_plus.wireless_transceiver": "无线收发器",
|
||||
"item.extendedae_plus.wireless_transceiver": "无线收发器",
|
||||
"gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式",
|
||||
"gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡",
|
||||
"gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍",
|
||||
"gui.extendedae_plus.global.all_on": "全部开启",
|
||||
"gui.extendedae_plus.global.all_off": "全部关闭",
|
||||
|
||||
"itemGroup.extendedae_plus.main": "扩展AE Plus",
|
||||
"item.extendedae_plus.wireless_transceiver": "无线收发器",
|
||||
"item.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
||||
"item.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
||||
"item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
||||
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
||||
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
"item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
|
||||
"item.extendedae_plus.entity_speed_card": "实体加速卡",
|
||||
"item.extendedae_plus.entity_speed_card.x2": "实体加速卡 (x2)",
|
||||
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
|
||||
"item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
|
||||
"item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
|
||||
"item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静",
|
||||
"tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物",
|
||||
"tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰",
|
||||
"tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
|
||||
"tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",
|
||||
|
||||
"block.extendedae_plus.wireless_transceiver": "无线收发器",
|
||||
"block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
||||
"block.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
||||
"block.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
||||
"block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
"block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
|
||||
|
||||
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
|
||||
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
|
||||
"extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
|
||||
|
|
@ -16,18 +46,17 @@
|
|||
"extendedae_plus.screen.cn_name": "中文名",
|
||||
"extendedae_plus.button.choose_provider":"上传样板",
|
||||
"extendedae_plus.pattern.hovertext.player": "由 %s 编码",
|
||||
|
||||
"block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
||||
"block.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
||||
"block.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
||||
"block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
|
||||
"item.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
||||
"item.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
||||
"item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
||||
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
||||
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker": "实体加速器",
|
||||
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",
|
||||
"screen.extendedae_plus.entity_speed_ticker.energy": "能耗: %s FE/t",
|
||||
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "功耗比例: %s",
|
||||
"screen.extendedae_plus.entity_speed_ticker.speed": "当前加速倍率: %d",
|
||||
"screen.extendedae_plus.entity_speed_ticker.multiplier": "额外消耗倍率: %s",
|
||||
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
|
||||
"item.extendedae_plus.entity_speed_ticker.tip.energy": "加速将消耗 AE 网络能量,网络能量不足时无法加速",
|
||||
|
||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
|
||||
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",
|
||||
|
|
@ -39,25 +68,22 @@
|
|||
"extendedae_plus.tooltip.master_mode": "模式: %s",
|
||||
"extendedae_plus.tooltip.locked": "锁定状态: %s",
|
||||
|
||||
"screen.extendedae_plus.title": "ExtendedAE Plus 配置",
|
||||
"config.extendedae_plus.pageMultiplier": "扩展样板供应器槽位倍率",
|
||||
"config.extendedae_plus.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
|
||||
"config.extendedae_plus.wirelessMaxRange": "无线最大距离",
|
||||
"config.extendedae_plus.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
|
||||
"config.extendedae_plus.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
|
||||
"config.extendedae_plus.providerRoundRobinEnable": "启用样板供应器轮询分配(智能翻倍)",
|
||||
"config.extendedae_plus.state_on": "开",
|
||||
"config.extendedae_plus.state_off": "关",
|
||||
"config.extendedae_plus.showEncoderPatternPlayer": "显示样板编码玩家",
|
||||
"config.extendedae_plus.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
|
||||
"block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
|
||||
"item.extendedae_plus.network_pattern_controller": "样板供应器状态控制器",
|
||||
"gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式",
|
||||
"gui.extendedae_plus.global.toggle_adv_blocking": "切换高级阻挡",
|
||||
"gui.extendedae_plus.global.toggle_smart_doubling": "切换智能翻倍",
|
||||
"gui.extendedae_plus.global.all_on": "全部开启",
|
||||
"gui.extendedae_plus.global.all_off": "全部关闭",
|
||||
"config.extendedae_plus.smartScalingMaxMultiplier": "智能倍增最大倍数",
|
||||
"config.extendedae_plus.smartScalingMaxMultiplier_with_range": "智能倍增最大倍数 (0为不限制)",
|
||||
"config.extendedae_plus.smartScalingMinBenefitFactor": "智能倍增最小收益因子 (默认 4)"
|
||||
"config.screen.extendedae_plus": "ExtendedAE Plus 配置",
|
||||
"config.extendedae_plus.option.pageMultiplier": "扩展样板供应器槽位倍率",
|
||||
"config.extendedae_plus.option.pageMultiplier_with_range": "扩展样板供应器槽位倍率",
|
||||
"config.extendedae_plus.option.wirelessMaxRange": "无线最大距离",
|
||||
"config.extendedae_plus.option.wirelessMaxRange_with_range": "无线最大距离",
|
||||
"config.extendedae_plus.option.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
|
||||
"config.extendedae_plus.option.providerRoundRobinEnable": "启用样板供应器轮询分配(智能翻倍)",
|
||||
"config.extendedae_plus.option.state_on": "开",
|
||||
"config.extendedae_plus.option.state_off": "关",
|
||||
"config.extendedae_plus.option.showEncoderPatternPlayer": "显示样板编码玩家",
|
||||
"config.extendedae_plus.option.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
|
||||
"config.extendedae_plus.option.smartScalingMaxMultiplier": "智能倍增最大倍数",
|
||||
"config.extendedae_plus.option.smartScalingMaxMultiplier_with_range": "智能倍增最大倍数",
|
||||
"config.extendedae_plus.option.smartScalingMinBenefitFactor": "智能倍增最小收益因子",
|
||||
"config.extendedae_plus.option.entityTickerCost": "实体加速器能量消耗基础值",
|
||||
"config.extendedae_plus.option.entityTickerBlackList": "实体加速器黑名单",
|
||||
"config.extendedae_plus.option.entityTickerMultipliers": "实体加速器额外消耗倍率",
|
||||
"config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值"
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"overrides": [
|
||||
{ "predicate": { "extendedae_plus:mult": 2.0 }, "model": "extendedae_plus:item/entity_speed_card_x2" },
|
||||
{ "predicate": { "extendedae_plus:mult": 4.0 }, "model": "extendedae_plus:item/entity_speed_card_x4" },
|
||||
{ "predicate": { "extendedae_plus:mult": 8.0 }, "model": "extendedae_plus:item/entity_speed_card_x8" },
|
||||
{ "predicate": { "extendedae_plus:mult": 16.0 }, "model": "extendedae_plus:item/entity_speed_card_x16" }
|
||||
],
|
||||
"textures": {
|
||||
"layer0": "extendedae_plus:item/entity_speed_card_x2"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x16" }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x2" }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x4" }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": { "layer0": "extendedae_plus:item/entity_speed_card_x8" }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "ae2:item/cable_interface",
|
||||
"textures": {
|
||||
"front": "extendedae_plus:part/entity_speed_ticker_font",
|
||||
"back": "extendedae_plus:part/entity_speed_ticker_back"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"parent": "item/generated",
|
||||
"textures": {
|
||||
"layer0": "extendedae_plus:item/infinity_biginteger_cell"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "ae2:part/interface_has_channel"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "ae2:part/interface_off"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "ae2:part/interface_on"
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"parent": "ae2:part/interface_base",
|
||||
"textures": {
|
||||
"front": "extendedae_plus:part/entity_speed_ticker_font",
|
||||
"sides": "extendedae_plus:part/entity_speed_ticker_sides",
|
||||
"back": "extendedae_plus:part/entity_speed_ticker_back",
|
||||
"particle": "extendedae_plus:part/entity_speed_ticker_back"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"animation": {
|
||||
"interpolate": true,
|
||||
"frames": [
|
||||
{
|
||||
"index": 0,
|
||||
"time": 32
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"time": 16
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user