Merge remote-tracking branch 'upstream/1.21.1' into 1.21.1merged
# Conflicts: # build.gradle # gradle.properties # src/main/java/com/extendedae_plus/ExtendedAEPlus.java # src/main/java/com/extendedae_plus/client/ClientProxy.java # src/main/java/com/extendedae_plus/client/InputEvents.java # src/main/java/com/extendedae_plus/config/ModConfig.java # src/main/java/com/extendedae_plus/init/ModBlockEntities.java # src/main/java/com/extendedae_plus/init/ModBlocks.java # src/main/java/com/extendedae_plus/integration/RecipeViewer/jei/JeiClientBootstrap.java # src/main/java/com/extendedae_plus/integration/jei/JeiRuntimeProxy.java # src/main/java/com/extendedae_plus/mixin/ae2/EncodedPatternItemMixin.java # src/main/java/com/extendedae_plus/mixin/ae2/menu/CraftConfirmMenuGoBackMixin.java # src/main/java/com/extendedae_plus/mixin/recipeViewer/MixinEncodingHelper.java # src/main/java/com/extendedae_plus/network/CraftingMonitorJumpC2SPacket.java # src/main/java/com/extendedae_plus/network/ProvidersListS2CPacket.java # src/main/java/com/extendedae_plus/network/ToggleAdvancedBlockingC2SPacket.java # src/main/java/com/extendedae_plus/network/ToggleSmartDoublingC2SPacket.java # src/main/java/com/extendedae_plus/wireless/WirelessSlaveLink.java # src/main/resources/assets/extendedae_plus/lang/en_us.json # src/main/resources/assets/extendedae_plus/lang/zh_cn.json # src/main/resources/extendedae_plus.mixins.json
This commit is contained in:
commit
0e29be2eff
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -26,3 +26,5 @@ run-data
|
||||||
repo
|
repo
|
||||||
|
|
||||||
othermods
|
othermods
|
||||||
|
crashreport
|
||||||
|
.cursor
|
||||||
16
build.gradle
16
build.gradle
|
|
@ -132,7 +132,7 @@ configurations {
|
||||||
dependencies {
|
dependencies {
|
||||||
// --- Added dependencies for target mods ---
|
// --- Added dependencies for target mods ---
|
||||||
implementation "curse.maven:glodium-957920:5821676"
|
implementation "curse.maven:glodium-957920:5821676"
|
||||||
implementation "org.appliedenergistics:appliedenergistics2:19.2.8"
|
implementation "org.appliedenergistics:appliedenergistics2:19.2.15"
|
||||||
implementation "org.appliedenergistics:guideme:2.5.1"
|
implementation "org.appliedenergistics:guideme:2.5.1"
|
||||||
// jarJar configuration not set in this build; use implementation for API for now
|
// jarJar configuration not set in this build; use implementation for API for now
|
||||||
implementation "de.mari_023:ae2wtlib_api:19.2.0"
|
implementation "de.mari_023:ae2wtlib_api:19.2.0"
|
||||||
|
|
@ -159,9 +159,21 @@ dependencies {
|
||||||
runtimeOnly "de.mari_023:ae2wtlib:19.2.1"
|
runtimeOnly "de.mari_023:ae2wtlib:19.2.1"
|
||||||
runtimeOnly "curse.maven:jade-324717:5427817"
|
runtimeOnly "curse.maven:jade-324717:5427817"
|
||||||
runtimeOnly "curse.maven:mega-cells-622112:6005043"
|
runtimeOnly "curse.maven:mega-cells-622112:6005043"
|
||||||
|
|
||||||
|
runtimeOnly "curse.maven:mekanism-268560:6895130"
|
||||||
|
runtimeOnly "curse.maven:applied-mekanistics-574300:5978711"
|
||||||
|
|
||||||
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
|
runtimeOnly "curse.maven:ex-pattern-provider-892005:6863556"
|
||||||
|
|
||||||
// setup Xei (EMI/REI/JEI) using project property 'use_Xei'
|
//aea
|
||||||
|
implementation "curse.maven:advancedae-1084104:6932981"
|
||||||
|
|
||||||
|
//geckolib
|
||||||
|
runtimeOnly "curse.maven:geckolib-388172:7009924"
|
||||||
|
|
||||||
|
|
||||||
|
runtimeOnly fileTree(dir: 'libs', includes: ['*.jar'])
|
||||||
|
|
||||||
runtimeOnly "dev.emi:emi-neoforge:${emi_version}+1.21"
|
runtimeOnly "dev.emi:emi-neoforge:${emi_version}+1.21"
|
||||||
runtimeOnly "mezz.jei:jei-1.21.1-neoforge:19.21.0.247"
|
runtimeOnly "mezz.jei:jei-1.21.1-neoforge:19.21.0.247"
|
||||||
runtimeOnly "curse.maven:ae2-jei-integration-1074338:5748513"
|
runtimeOnly "curse.maven:ae2-jei-integration-1074338:5748513"
|
||||||
|
|
|
||||||
103
build/generated/sources/modMetadata/META-INF/neoforge.mods.toml
Normal file
103
build/generated/sources/modMetadata/META-INF/neoforge.mods.toml
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
# This is an example neoforge.mods.toml file. It contains the data relating to the loading mods.
|
||||||
|
# There are several mandatory fields (#mandatory), and many more that are optional (#optional).
|
||||||
|
# The overall format is standard TOML format, v0.5.0.
|
||||||
|
# Note that there are a couple of TOML lists in this file.
|
||||||
|
# Find more information on toml format here: https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml
|
||||||
|
modLoader="javafml" #mandatory
|
||||||
|
|
||||||
|
# A version range to match for said mod loader - for regular FML @Mod it will be the FML version. This is currently 2.
|
||||||
|
loaderVersion="[1,)" #mandatory
|
||||||
|
|
||||||
|
# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.
|
||||||
|
# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.
|
||||||
|
license="All Rights Reserved"
|
||||||
|
|
||||||
|
# A URL to refer people to when problems occur with this mod
|
||||||
|
#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional
|
||||||
|
|
||||||
|
# A list of mods - how many allowed here is determined by the individual mod loader
|
||||||
|
[[mods]] #mandatory
|
||||||
|
|
||||||
|
# The modid of the mod
|
||||||
|
modId="extendedae_plus" #mandatory
|
||||||
|
|
||||||
|
# The version number of the mod
|
||||||
|
version="1.21.1-1.4.3" #mandatory
|
||||||
|
|
||||||
|
# A display name for the mod
|
||||||
|
displayName="ExtendedAE-Plus" #mandatory
|
||||||
|
|
||||||
|
# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/
|
||||||
|
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
|
||||||
|
|
||||||
|
# A URL for the "homepage" for this mod, displayed in the mod UI
|
||||||
|
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
||||||
|
|
||||||
|
# A file name (in the root of the mod JAR) containing a logo for display
|
||||||
|
#logoFile="examplemod.png" #optional
|
||||||
|
|
||||||
|
# A text field displayed in the mod UI
|
||||||
|
#credits="" #optional
|
||||||
|
|
||||||
|
# A text field displayed in the mod UI
|
||||||
|
authors="YourNameHere, OtherNameHere" #optional
|
||||||
|
|
||||||
|
# The description text for the mod (multi line!) (#mandatory)
|
||||||
|
description='''Example mod description.
|
||||||
|
Newline characters can be used and will be replaced properly.'''
|
||||||
|
|
||||||
|
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
|
||||||
|
[[mixins]]
|
||||||
|
config="extendedae_plus.mixins.json"
|
||||||
|
|
||||||
|
# The [[accessTransformers]] block allows you to declare where your AT file is.
|
||||||
|
# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg
|
||||||
|
#[[accessTransformers]]
|
||||||
|
#file="META-INF/accesstransformer.cfg"
|
||||||
|
|
||||||
|
# The coremods config file path is not configurable and is always loaded from META-INF/coremods.json
|
||||||
|
|
||||||
|
# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.
|
||||||
|
[[dependencies.extendedae_plus]] #optional
|
||||||
|
# the modid of the dependency
|
||||||
|
modId="neoforge" #mandatory
|
||||||
|
# The type of the dependency. Can be one of "required", "optional", "incompatible" or "discouraged" (case insensitive).
|
||||||
|
# 'required' requires the mod to exist, 'optional' does not
|
||||||
|
# 'incompatible' will prevent the game from loading when the mod exists, and 'discouraged' will show a warning
|
||||||
|
type="required" #mandatory
|
||||||
|
# Optional field describing why the dependency is required or why it is incompatible
|
||||||
|
# reason="..."
|
||||||
|
# The version range of the dependency
|
||||||
|
versionRange="[21.1.1,)" #mandatory
|
||||||
|
# An ordering relationship for the dependency.
|
||||||
|
# BEFORE - This mod is loaded BEFORE the dependency
|
||||||
|
# AFTER - This mod is loaded AFTER the dependency
|
||||||
|
ordering="NONE"
|
||||||
|
# Side this dependency is applied on - BOTH, CLIENT, or SERVER
|
||||||
|
side="BOTH"
|
||||||
|
|
||||||
|
# Here's another dependency
|
||||||
|
[[dependencies.extendedae_plus]]
|
||||||
|
modId="minecraft"
|
||||||
|
type="required"
|
||||||
|
# This version range declares a minimum of the current minecraft version up to but not including the next major version
|
||||||
|
versionRange="[1.21.1]"
|
||||||
|
ordering="NONE"
|
||||||
|
side="BOTH"
|
||||||
|
|
||||||
|
# Require ExtendedAE (ExtendedAE-1.21-2.2.21-neoforge) to be present
|
||||||
|
[[dependencies.extendedae_plus]]
|
||||||
|
modId="extendedae"
|
||||||
|
type="required"
|
||||||
|
# Use a permissive range to tolerate upstream version string variations (e.g. 1.21-2.2.21-neoforge)
|
||||||
|
versionRange="*"
|
||||||
|
ordering="AFTER"
|
||||||
|
side="BOTH"
|
||||||
|
|
||||||
|
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
|
||||||
|
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
|
||||||
|
# stop your mod loading on the server for example.
|
||||||
|
#[features.extendedae_plus]
|
||||||
|
#openGLVersion="[3.2,)"
|
||||||
|
|
@ -14,8 +14,8 @@ loader_version_range=[1,)
|
||||||
## Mod Properties
|
## Mod Properties
|
||||||
mod_id=extendedae_plus
|
mod_id=extendedae_plus
|
||||||
mod_name=ExtendedAE-Plus
|
mod_name=ExtendedAE-Plus
|
||||||
|
mod_version=1.21.1-1.4.3
|
||||||
mod_license=LGPL-3.0-or-later
|
mod_license=LGPL-3.0-or-later
|
||||||
mod_version=1.0.2jei
|
|
||||||
mod_group_id=com.extendedae_plus
|
mod_group_id=com.extendedae_plus
|
||||||
mod_authors=GaLicn, XianYuFish001
|
mod_authors=GaLicn, XianYuFish001
|
||||||
mod_description=
|
mod_description=
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.extendedae_plus;
|
package com.extendedae_plus;
|
||||||
|
|
||||||
|
import appeng.api.parts.IPart;
|
||||||
|
import appeng.api.parts.PartModels;
|
||||||
import appeng.api.storage.StorageCells;
|
import appeng.api.storage.StorageCells;
|
||||||
import appeng.block.AEBaseEntityBlock;
|
import appeng.block.AEBaseEntityBlock;
|
||||||
import appeng.blockentity.crafting.CraftingBlockEntity;
|
import appeng.blockentity.crafting.CraftingBlockEntity;
|
||||||
|
import appeng.items.parts.PartModelsHelper;
|
||||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
|
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
|
||||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||||
import com.extendedae_plus.config.ModConfig;
|
import com.extendedae_plus.config.ModConfig;
|
||||||
|
|
@ -11,7 +14,7 @@ import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
|
@ -19,8 +22,10 @@ import net.neoforged.fml.ModContainer;
|
||||||
import net.neoforged.fml.common.Mod;
|
import net.neoforged.fml.common.Mod;
|
||||||
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||||
import net.neoforged.neoforge.common.NeoForge;
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
import net.neoforged.neoforge.event.level.LevelEvent;
|
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
||||||
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
||||||
|
import net.neoforged.neoforge.event.server.ServerStoppedEvent;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@Mod(ExtendedAEPlus.MODID)
|
@Mod(ExtendedAEPlus.MODID)
|
||||||
|
|
@ -40,12 +45,12 @@ public class ExtendedAEPlus {
|
||||||
ModMenuTypes.MENUS.register(modEventBus);
|
ModMenuTypes.MENUS.register(modEventBus);
|
||||||
|
|
||||||
NeoForge.EVENT_BUS.register(this);
|
NeoForge.EVENT_BUS.register(this);
|
||||||
NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
|
NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onServerStarted);
|
||||||
|
NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onServerStopped);
|
||||||
NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
|
// 注册配置:接入自定义的 ModConfigs
|
||||||
NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping);
|
modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC, "extendedae_plus-common.toml");
|
||||||
|
modContainer.registerConfig(ModConfig.Type.CLIENT, ModConfigs.CLIENT_SPEC, "extendedae_plus-client.toml");
|
||||||
modContainer.registerConfig(net.neoforged.fml.config.ModConfig.Type.COMMON, ModConfig.COMMON_SPEC);
|
modContainer.registerConfig(ModConfig.Type.SERVER, ModConfigs.SERVER_SPEC, "extendedae_plus-server.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceLocation id(String path) {
|
public static ResourceLocation id(String path) {
|
||||||
|
|
@ -56,8 +61,20 @@ public class ExtendedAEPlus {
|
||||||
LOGGER.info("HELLO FROM COMMON SETUP");
|
LOGGER.info("HELLO FROM COMMON SETUP");
|
||||||
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
|
LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT));
|
||||||
StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
|
StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
|
||||||
|
|
||||||
|
// 绑定 AE2 的 CraftingBlockEntity 到本模组的自定义加速器方块,避免 AEBaseEntityBlock.blockEntityType 为空
|
||||||
event.enqueueWork(() -> {
|
event.enqueueWork(() -> {
|
||||||
try {
|
try {
|
||||||
|
// 注册升级卡
|
||||||
|
new UpgradeCards(event);
|
||||||
|
|
||||||
|
// 为 PartItem 注册 AE2 部件模型
|
||||||
|
PartModels.registerModels(
|
||||||
|
PartModelsHelper.createModels(
|
||||||
|
ModItems.ENTITY_TICKER_PART_ITEM.get().getPartClass().asSubclass(IPart.class)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// 注册自定义 AE2 MenuLocator(用于 Curios 槽位打开菜单)
|
// 注册自定义 AE2 MenuLocator(用于 Curios 槽位打开菜单)
|
||||||
try {
|
try {
|
||||||
appeng.menu.locator.MenuLocators.register(
|
appeng.menu.locator.MenuLocators.register(
|
||||||
|
|
@ -85,22 +102,47 @@ public class ExtendedAEPlus {
|
||||||
b256.setBlockEntity(CraftingBlockEntity.class, type, null, null);
|
b256.setBlockEntity(CraftingBlockEntity.class, type, null, null);
|
||||||
b1024.setBlockEntity(CraftingBlockEntity.class, type, null, null);
|
b1024.setBlockEntity(CraftingBlockEntity.class, type, null, null);
|
||||||
LOGGER.info("Bound AE2 CraftingBlockEntity to ExtendedAE Plus accelerators.");
|
LOGGER.info("Bound AE2 CraftingBlockEntity to ExtendedAE Plus accelerators.");
|
||||||
|
|
||||||
|
// 绑定装配矩阵上传核心方块实体类型,避免 blockEntityClass 为 null 的问题
|
||||||
|
ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get().setBlockEntity(
|
||||||
|
com.extendedae_plus.content.matrix.UploadCoreBlockEntity.class,
|
||||||
|
ModBlockEntities.UPLOAD_CORE_BE.get(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
LOGGER.info("Bound UploadCoreBlockEntity to assembler matrix upload core block.");
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LOGGER.warn("Failed to bind CraftingBlockEntity to accelerators: {}", t.toString());
|
LOGGER.warn("Failed to bind block entities: {}", t.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static InfinityStorageManager storageManager;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static MinecraftServer storageManagerServer;
|
||||||
|
|
||||||
|
private static void onServerStarted(ServerStartedEvent event) {
|
||||||
|
storageManagerServer = event.getServer();
|
||||||
|
storageManager = InfinityStorageManager.getInstance(event.getServer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void onServerStopped(ServerStoppedEvent event) {
|
||||||
|
if (storageManagerServer == event.getServer()) {
|
||||||
|
storageManagerServer = null;
|
||||||
|
storageManager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static InfinityStorageManager currentStorageManager() {
|
||||||
|
return storageManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can use SubscribeEvent and let the Event Bus discover methods to call
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void onServerStarting(ServerStartingEvent event) {
|
public void onServerStarting(ServerStartingEvent event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 在世界加载时注册/加载 SavedData
|
|
||||||
private static void onLevelLoad(LevelEvent.Load event) {
|
|
||||||
if (event.getLevel() instanceof ServerLevel serverLevel) {
|
|
||||||
InfinityStorageManager.getForLevel(serverLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,35 @@ import net.neoforged.fml.common.Mod;
|
||||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
|
import net.neoforged.neoforge.client.gui.ConfigurationScreen;
|
||||||
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
|
import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
|
||||||
|
import net.neoforged.fml.ModList;
|
||||||
|
|
||||||
// This class will not load on dedicated servers. Accessing client side code from here is safe.
|
// This class will not load on dedicated servers. Accessing client side code from here is safe.
|
||||||
@Mod(value = ExtendedAEPlus.MODID, dist = Dist.CLIENT)
|
@Mod(value = ExtendedAEPlus.MODID, dist = Dist.CLIENT)
|
||||||
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
|
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
|
||||||
@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT)
|
@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT)
|
||||||
public class ExtendedAEPlusClient {
|
public class ExtendedAEPlusClient {
|
||||||
public ExtendedAEPlusClient(ModContainer container) {
|
public ExtendedAEPlusClient(ModContainer container) {
|
||||||
// Allows NeoForge to create a config screen for this mod's configs.
|
// Allows NeoForge to create a config screen for this mod's configs.
|
||||||
// The config screen is accessed by going to the Mods screen > clicking on your mod > clicking on config.
|
// The config screen is accessed by going to the Mods screen > clicking on your mod > clicking on config.
|
||||||
// Do not forget to add translations for your config options to the en_us.json file.
|
// Do not forget to add translations for your config options to the en_us.json file.
|
||||||
container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new);
|
container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
static void onClientSetup(FMLClientSetupEvent event) {
|
static void onClientSetup(FMLClientSetupEvent event) {
|
||||||
// Some client setup code
|
// Some client setup code
|
||||||
ExtendedAEPlus.LOGGER.info("HELLO FROM CLIENT SETUP");
|
ExtendedAEPlus.LOGGER.info("HELLO FROM CLIENT SETUP");
|
||||||
ExtendedAEPlus.LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
ExtendedAEPlus.LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
||||||
}
|
|
||||||
|
// Register JEI-dependent input handlers only when JEI is present
|
||||||
|
if (ModList.get().isLoaded("jei")) {
|
||||||
|
try {
|
||||||
|
Class<?> bootstrap = Class.forName("com.extendedae_plus.integration.jei.JeiClientBootstrap");
|
||||||
|
java.lang.reflect.Method m = bootstrap.getMethod("register");
|
||||||
|
m.invoke(null);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAEPlus.LOGGER.warn("Failed to register JEI client listeners: {}", t.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,21 @@ package com.extendedae_plus.ae.api.storage;
|
||||||
|
|
||||||
import appeng.api.storage.cells.ICellHandler;
|
import appeng.api.storage.cells.ICellHandler;
|
||||||
import appeng.api.storage.cells.ISaveProvider;
|
import appeng.api.storage.cells.ISaveProvider;
|
||||||
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
/**
|
|
||||||
* InfinityBigIntegerCellHandler
|
|
||||||
*
|
|
||||||
* 该类实现 AE2 的 ICellHandler,用于:
|
|
||||||
* - 判定某个 ItemStack 是否为本 mod 的 Infinity 存储单元
|
|
||||||
* - 在 AE2 请求访问或创建存储单元时,创建并返回对应的 StorageCell 实例
|
|
||||||
*/
|
|
||||||
public class InfinityBigIntegerCellHandler implements ICellHandler {
|
public class InfinityBigIntegerCellHandler implements ICellHandler {
|
||||||
|
|
||||||
/** Handler 单例,供注册与调用使用 */
|
|
||||||
public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler();
|
public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler();
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断给定的 ItemStack 是否为 InfinityBigIntegerCell
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCell(ItemStack is) {
|
public boolean isCell(ItemStack is) {
|
||||||
return is.getItem() instanceof InfinityBigIntegerCellItem;
|
return is.getItem() instanceof InfinityBigIntegerCellItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 在 AE2 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventory(StorageCell 实现)。
|
|
||||||
* 参数 container 为 AE2 提供的保存回调(ISaveProvider),当 cell 需要持久化时会调用它。
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
|
public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
|
||||||
return InfinityBigIntegerCellInventory.createInventory(is, container);
|
return InfinityBigIntegerCellInventory.createInventory(is, container, ExtendedAEPlus.currentStorageManager());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,127 +8,70 @@ import appeng.api.stacks.KeyCounter;
|
||||||
import appeng.api.storage.cells.CellState;
|
import appeng.api.storage.cells.CellState;
|
||||||
import appeng.api.storage.cells.ISaveProvider;
|
import appeng.api.storage.cells.ISaveProvider;
|
||||||
import appeng.api.storage.cells.StorageCell;
|
import appeng.api.storage.cells.StorageCell;
|
||||||
|
import appeng.core.AELog;
|
||||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||||
|
import com.extendedae_plus.util.storage.InfinityConstants;
|
||||||
import com.extendedae_plus.util.storage.InfinityDataStorage;
|
import com.extendedae_plus.util.storage.InfinityDataStorage;
|
||||||
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
import com.extendedae_plus.util.storage.InfinityStorageManager;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.HolderLookup;
|
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.nbt.ListTag;
|
import net.minecraft.nbt.ListTag;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.text.DecimalFormat;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InfinityBigIntegerCellInventory
|
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||||
* <p>
|
* Original copyright (c) Technici4n<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 {
|
public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||||
|
private final InfinityBigIntegerCellItem cell;
|
||||||
// 待持久化队列(用于 debounce:在服务器 tick 中合并持久化)
|
// 磁盘本身
|
||||||
private static final ConcurrentLinkedQueue<InfinityBigIntegerCellInventory> PENDING_PERSIST = new ConcurrentLinkedQueue<>();
|
private final ItemStack self;
|
||||||
// 数字格式化对象,保留两位小数(复用以减少对象分配)
|
@Nullable
|
||||||
private static final DecimalFormat DF = new DecimalFormat("#.##");
|
private final InfinityStorageManager storageManager;
|
||||||
|
|
||||||
// 关联的 ItemStack(含可能的 uuid NBT)
|
|
||||||
private final ItemStack stack;
|
|
||||||
// AE2 提供的保存提供者,用于在容器中批量保存时触发回调
|
// AE2 提供的保存提供者,用于在容器中批量保存时触发回调
|
||||||
private final ISaveProvider container;
|
private final ISaveProvider container;
|
||||||
// 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化)
|
// 存储物品键和数量的映射
|
||||||
private Object2ObjectMap<AEKey, BigInteger> storedMap = null;
|
private Object2ObjectMap<AEKey, BigInteger> AEKey2AmountsMap;
|
||||||
|
// 存储的物品种类数量
|
||||||
|
private int totalAEKeyType;
|
||||||
|
// 存储的物品总数
|
||||||
|
private BigInteger totalAEKey2Amounts = BigInteger.ZERO;
|
||||||
// 标记是否已持久化到 SavedData
|
// 标记是否已持久化到 SavedData
|
||||||
private boolean isPersisted = true;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建存储单元库存实例的静态方法
|
public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell,
|
||||||
static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) {
|
ItemStack stack,
|
||||||
if (stack.getItem() instanceof InfinityBigIntegerCellItem) {
|
ISaveProvider saveProvider,
|
||||||
return new InfinityBigIntegerCellInventory(stack, saveProvider);
|
@Nullable InfinityStorageManager storageManager) {
|
||||||
}
|
// 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性
|
||||||
return null;
|
this.cell = cell;
|
||||||
}
|
// 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据
|
||||||
|
this.self = stack;
|
||||||
// 获取全局存储实例
|
// 保存提供者,用于触发数据保存
|
||||||
private static InfinityStorageManager getStorageInstance() {
|
this.container = saveProvider;
|
||||||
return InfinityStorageManager.INSTANCE;
|
// 初始化 storedAmounts 为 null,延迟加载物品数据
|
||||||
}
|
this.AEKey2AmountsMap = null;
|
||||||
|
this.storageManager = storageManager;
|
||||||
// 服务器 tick 回调:合并并执行待持久化项
|
// 初始化磁盘数据
|
||||||
public static void onServerTick(ServerTickEvent.Post event) {
|
initData();
|
||||||
// NeoForge 提供 Pre/Post 子类以区分阶段,Post 对应原来的 Phase.END
|
|
||||||
InfinityBigIntegerCellInventory inv;
|
|
||||||
// 处理本次 tick 中的全部待持久化项
|
|
||||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
|
||||||
try {
|
|
||||||
if (!inv.isPersisted) {
|
|
||||||
inv.persist();
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
// 忽略单项错误,继续处理其余队列
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在服务器停止时被调用,立即强制持久化队列中的所有实例
|
|
||||||
public static void onServerStopping(ServerStoppingEvent event) {
|
|
||||||
InfinityBigIntegerCellInventory inv;
|
|
||||||
while ((inv = PENDING_PERSIST.poll()) != null) {
|
|
||||||
try {
|
|
||||||
if (!inv.isPersisted) {
|
|
||||||
inv.persist();
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
// 忽略单项错误,继续尝试持久化其它实例
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发)
|
|
||||||
try {
|
|
||||||
var stor = getStorageInstance();
|
|
||||||
if (stor != null) stor.setDirty();
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将 BigInteger 格式化为带单位的字符串,保留两位小数
|
// 将 BigInteger 格式化为带单位的字符串,保留两位小数
|
||||||
public static String formatBigInteger(BigInteger number) {
|
public static String formatBigInteger(BigInteger number) {
|
||||||
// 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行
|
// 使用方法局部的 DecimalFormat,避免静态共享的非线程安全问题
|
||||||
|
java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");
|
||||||
BigDecimal bd = new BigDecimal(number);
|
BigDecimal bd = new BigDecimal(number);
|
||||||
BigDecimal thousand = new BigDecimal(1000);
|
BigDecimal thousand = new BigDecimal(1000);
|
||||||
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
|
String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"};
|
||||||
|
|
@ -140,26 +83,60 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||||
if (idx == 0) {
|
if (idx == 0) {
|
||||||
return bd.setScale(0, RoundingMode.DOWN).toPlainString();
|
return bd.setScale(0, RoundingMode.DOWN).toPlainString();
|
||||||
}
|
}
|
||||||
return DF.format(bd.doubleValue()) + units[idx];
|
return df.format(bd.doubleValue()) + units[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前存储单元的数据存储对象
|
// 静态方法,创建存储单元库存
|
||||||
|
public static InfinityBigIntegerCellInventory createInventory(ItemStack stack,
|
||||||
|
ISaveProvider saveProvider,
|
||||||
|
@Nullable InfinityStorageManager storageManager) {
|
||||||
|
// 检查物品堆栈是否为空
|
||||||
|
Objects.requireNonNull(stack, "Cannot create cell inventory for null itemstack");
|
||||||
|
// 检查物品是否为 IDISKCellItem 类型
|
||||||
|
if (!(stack.getItem() instanceof InfinityBigIntegerCellItem cell)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 创建并返回新的 DISKCellInventory 实例
|
||||||
|
return new InfinityBigIntegerCellInventory(cell, stack, saveProvider, storageManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取磁盘的 InfinityDataStorage 数据
|
||||||
private InfinityDataStorage getCellStorage() {
|
private InfinityDataStorage getCellStorage() {
|
||||||
if (this.getUUID() == null) {
|
// 如果磁盘有 UUID,返回对应的 InfinityDataStorage
|
||||||
// 如果没有UUID,返回空存储
|
if (getUUID() != null && this.storageManager != null) {
|
||||||
return InfinityDataStorage.EMPTY;
|
return storageManager.getOrCreateCell(getUUID());
|
||||||
} else {
|
} else {
|
||||||
// 否则获取或创建对应UUID的存储
|
// 否则返回空的 InfinityDataStorage
|
||||||
return getStorageInstance().getOrCreateCell(getUUID());
|
return InfinityDataStorage.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取存储单元状态(空/非空)
|
// 初始化磁盘数据
|
||||||
|
private void initData() {
|
||||||
|
// 如果磁盘有 UUID,加载存储的物品数据
|
||||||
|
if (hasUUID()) {
|
||||||
|
this.totalAEKeyType = getCellStorage().amounts.size();
|
||||||
|
this.totalAEKey2Amounts = getCellStorage().itemCount.equals(BigInteger.ZERO) ?
|
||||||
|
BigInteger.ZERO :
|
||||||
|
getCellStorage().itemCount;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 否则初始化为空
|
||||||
|
this.totalAEKeyType = 0;
|
||||||
|
this.totalAEKey2Amounts = BigInteger.ZERO;
|
||||||
|
// 加载物品数据
|
||||||
|
getCellStoredMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取存储单元的状态(空、部分填充)
|
||||||
@Override
|
@Override
|
||||||
public CellState getStatus() {
|
public CellState getStatus() {
|
||||||
if (this.getCellStoredMap().isEmpty()) {
|
// 如果没有存储任何物品,返回空状态
|
||||||
|
if (this.getTotalAEKey2Amounts().equals(BigInteger.ZERO)) {
|
||||||
return CellState.EMPTY;
|
return CellState.EMPTY;
|
||||||
}
|
}
|
||||||
|
// 否则返回满状态
|
||||||
return CellState.NOT_EMPTY;
|
return CellState.NOT_EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,222 +146,236 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||||
return 512;
|
return 512;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 持久化存储单元数据到全局存储
|
||||||
|
@Override
|
||||||
|
public void persist() {
|
||||||
|
if (this.isPersisted || this.storageManager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.totalAEKey2Amounts.equals(BigInteger.ZERO)) {
|
||||||
|
if (hasUUID()) {
|
||||||
|
this.storageManager.removeCell(getUUID());
|
||||||
|
// 从 DataComponents.CUSTOM_DATA 里移除对应字段
|
||||||
|
CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
|
CompoundTag tag = data.copyTag();
|
||||||
|
tag.remove(InfinityConstants.INFINITY_CELL_UUID);
|
||||||
|
tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL);
|
||||||
|
tag.remove(InfinityConstants.INFINITY_ITEM_TYPES);
|
||||||
|
// backward compat
|
||||||
|
tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
|
||||||
|
|
||||||
|
self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
|
||||||
|
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建物品键列表
|
||||||
|
ListTag keys = new ListTag();
|
||||||
|
// 创建物品数量列表
|
||||||
|
ListTag amounts = new ListTag();
|
||||||
|
// 初始化物品总数
|
||||||
|
BigInteger itemCount = BigInteger.ZERO;
|
||||||
|
|
||||||
|
for (var entry : this.AEKey2AmountsMap.object2ObjectEntrySet()) {
|
||||||
|
BigInteger amount = entry.getValue();
|
||||||
|
// 如果数量大于 0,添加到键和数量列表
|
||||||
|
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
keys.add(entry.getKey().toTagGeneric(this.storageManager.getRegistries()));
|
||||||
|
CompoundTag amountTag = new CompoundTag();
|
||||||
|
amountTag.putByteArray("value", amount.toByteArray());
|
||||||
|
amounts.add(amountTag);
|
||||||
|
|
||||||
|
itemCount = itemCount.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
this.storageManager.updateCell(getUUID(), new InfinityDataStorage());
|
||||||
|
} else {
|
||||||
|
this.storageManager.modifyDisk(getUUID(), keys, amounts, itemCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新存储的物品种类数量
|
||||||
|
this.totalAEKeyType = this.AEKey2AmountsMap.size();
|
||||||
|
// 更新存储的物品总数
|
||||||
|
this.totalAEKey2Amounts = itemCount;
|
||||||
|
// 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本
|
||||||
|
|
||||||
|
// 写回 DataComponents.CUSTOM_DATA(替代 getOrCreateTag)
|
||||||
|
CompoundTag tag = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
|
||||||
|
tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray());
|
||||||
|
tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType);
|
||||||
|
tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray());
|
||||||
|
self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
|
||||||
|
// 标记数据已持久化
|
||||||
|
this.isPersisted = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取存储单元的描述(此处返回null,可自定义)
|
// 获取存储单元的描述(此处返回null,可自定义)
|
||||||
@Override
|
@Override
|
||||||
public Component getDescription() {
|
public Component getDescription() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取存储的物品总数
|
||||||
|
public BigInteger getTotalAEKey2Amounts() {
|
||||||
|
return this.totalAEKey2Amounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取存储的物品种类数量
|
||||||
|
public int getTotalAEKeyType() {
|
||||||
|
return this.totalAEKeyType;
|
||||||
|
}
|
||||||
|
|
||||||
// 判断物品堆栈是否有UUID
|
// 判断物品堆栈是否有UUID
|
||||||
public boolean hasUUID() {
|
public boolean hasUUID() {
|
||||||
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
|
CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
if (customData == null) return false;
|
return !data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID);
|
||||||
CompoundTag tag = customData.copyTag();
|
|
||||||
return tag != null && tag.contains("uuid");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取物品堆栈的UUID
|
// 获取物品堆栈的UUID
|
||||||
public UUID getUUID() {
|
public UUID getUUID() {
|
||||||
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
|
CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
if (customData == null) return null;
|
if (!data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID)) {
|
||||||
CompoundTag tag = customData.copyTag();
|
return data.copyTag().getUUID(InfinityConstants.INFINITY_CELL_UUID);
|
||||||
if (tag != null && tag.contains("uuid")) {
|
|
||||||
return tag.getUUID("uuid");
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取或初始化存储映射
|
// 获取或初始化存储映射
|
||||||
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
|
private Object2ObjectMap<AEKey, BigInteger> getCellStoredMap() {
|
||||||
if (storedMap == null) {
|
if (AEKey2AmountsMap == null) {
|
||||||
storedMap = new Object2ObjectOpenHashMap<>();
|
AEKey2AmountsMap = new Object2ObjectOpenHashMap<>();
|
||||||
this.loadCellStoredMap();
|
this.loadCellStoredMap();
|
||||||
}
|
}
|
||||||
return storedMap;
|
return AEKey2AmountsMap;
|
||||||
}
|
|
||||||
|
|
||||||
// 从存储中加载物品映射
|
|
||||||
private void loadCellStoredMap() {
|
|
||||||
boolean corruptedTag = false; // 标记数据是否损坏
|
|
||||||
if (!this.hasUUID()) return;
|
|
||||||
|
|
||||||
ListTag keys = this.getCellStorage().keys;
|
|
||||||
ListTag amounts = this.getCellStorage().amounts;
|
|
||||||
|
|
||||||
int len = Math.min(keys.size(), amounts.size());
|
|
||||||
|
|
||||||
HolderLookup.Provider registries = getStorageInstance().getRegistries();
|
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
|
||||||
AEKey key = AEKey.fromTagGeneric(registries,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
|
@Override
|
||||||
public void getAvailableStacks(KeyCounter out) {
|
public void getAvailableStacks(KeyCounter out) {
|
||||||
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
|
||||||
Object2ObjectMap<AEKey, BigInteger> map = getCellStoredMap();
|
if (this.getCellStoredMap() == null) return;
|
||||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) {
|
||||||
AEKey key = entry.getKey();
|
AEKey key = entry.getKey();
|
||||||
BigInteger value = entry.getValue();
|
BigInteger value = entry.getValue();
|
||||||
|
|
||||||
// 当前 KeyCounter 中已有的值(long)
|
// 获取 KeyCounter 中已有的值
|
||||||
long existing = out.get(key);
|
long existing = out.get(key);
|
||||||
|
|
||||||
// 将 existing 与当前 value 做 BigInteger 累加并饱和到 Long.MAX_VALUE
|
// 计算总和并限制到 Long.MAX_VALUE
|
||||||
BigInteger sum = BigInteger.valueOf(existing).add(value);
|
BigInteger sum = BigInteger.valueOf(existing).add(value);
|
||||||
long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue();
|
long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue();
|
||||||
|
// 更新 KeyCounter
|
||||||
// KeyCounter 没有 set(key,long) 的统一接口暴露(只有 add/remove),所以先移除已存在的值再设置。
|
|
||||||
// 为避免读取-写入竞争,我们计算出要新增的 delta 并调用 add(key, delta)
|
|
||||||
if (existing == Long.MAX_VALUE) {
|
if (existing == Long.MAX_VALUE) {
|
||||||
// 已经饱和,无需再添加
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
long delta;
|
long delta = toSet - existing;
|
||||||
if (toSet == Long.MAX_VALUE) {
|
|
||||||
delta = Long.MAX_VALUE - existing;
|
|
||||||
} else {
|
|
||||||
delta = toSet - existing;
|
|
||||||
}
|
|
||||||
if (delta != 0) {
|
if (delta != 0) {
|
||||||
out.add(key, delta);
|
out.add(key, delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 持久化存储单元数据到全局存储
|
|
||||||
@Override
|
// 从存储中加载物品映射
|
||||||
public void persist() {
|
private void loadCellStoredMap() {
|
||||||
if (this.isPersisted)
|
if (this.storageManager == null) {
|
||||||
return;
|
|
||||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
|
||||||
if (map.isEmpty()) {
|
|
||||||
// 如果存储为空,移除UUID和全局存储中的数据
|
|
||||||
if (this.hasUUID()) {
|
|
||||||
getStorageInstance().removeCell(getUUID());
|
|
||||||
// 从 CustomData 中移除缓存字段
|
|
||||||
try {
|
|
||||||
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
|
|
||||||
tag.remove("uuid");
|
|
||||||
tag.remove("total");
|
|
||||||
});
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 构建要保存的Key和数量列表(混合表示:long 或 string)
|
|
||||||
ListTag amountTags = new ListTag();
|
boolean dataCorruption = false;
|
||||||
ListTag keys = new ListTag();
|
if (self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).isEmpty()) return;
|
||||||
for (Object2ObjectMap.Entry<AEKey, BigInteger> entry : map.object2ObjectEntrySet()) {
|
|
||||||
BigInteger amount = entry.getValue();
|
var keys = getCellStorage().keys;
|
||||||
if (amount.compareTo(BigInteger.ZERO) > 0) {
|
var amounts = getCellStorage().amounts;
|
||||||
keys.add(entry.getKey().toTagGeneric(getStorageInstance().getRegistries()));
|
// 数据损坏
|
||||||
CompoundTag amt = new CompoundTag();
|
if (keys.size() != amounts.size()) {
|
||||||
if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
AELog.warn("Loading storage cell with mismatched amounts/tags: %d != %d", amounts.size(), keys.size());
|
||||||
amt.putLong("l", amount.longValue());
|
}
|
||||||
} else {
|
|
||||||
amt.putString("s", amount.toString());
|
var registries = this.storageManager.getRegistries();
|
||||||
}
|
|
||||||
amountTags.add(amt);
|
// 遍历数量和键,加载到 AEKey2AmountsMap
|
||||||
|
for (int i = 0; i < amounts.size(); i++) {
|
||||||
|
AEKey key = AEKey.fromTagGeneric(registries,keys.getCompound(i));
|
||||||
|
BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value"));
|
||||||
|
// 检查数据是否损坏
|
||||||
|
if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
|
||||||
|
dataCorruption = true;
|
||||||
|
} else {
|
||||||
|
AEKey2AmountsMap.put(key, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 如果没有Key,更新为空存储,否则保存数据
|
if (dataCorruption) {
|
||||||
if (keys.isEmpty()) {
|
this.saveChanges();
|
||||||
getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记数据需要保存,并通知容器或直接持久化
|
||||||
|
private void saveChanges() {
|
||||||
|
// 更新存储的物品种类数量
|
||||||
|
this.totalAEKeyType = this.AEKey2AmountsMap.size();
|
||||||
|
// 重置物品总数
|
||||||
|
this.totalAEKey2Amounts = BigInteger.ZERO;
|
||||||
|
// 计算物品总数
|
||||||
|
for (BigInteger AEKey2Amounts : this.AEKey2AmountsMap.values()) {
|
||||||
|
this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(AEKey2Amounts);
|
||||||
|
}
|
||||||
|
// 标记数据未持久化
|
||||||
|
this.isPersisted = false;
|
||||||
|
// 如果有保存提供者,通知保存
|
||||||
|
if (this.container != null) {
|
||||||
|
this.container.saveChanges();
|
||||||
} else {
|
} else {
|
||||||
// amounts 现在为 CompoundTag 列表
|
// 否则立即持久化
|
||||||
getStorageInstance().modifyCell(this.getUUID(), keys, amountTags);
|
this.persist();
|
||||||
}
|
}
|
||||||
// 将缓存的 totalStored 同步到 ItemStack 的 CustomData(优先使用 long)
|
|
||||||
try {
|
|
||||||
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
|
|
||||||
if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
|
|
||||||
tag.putLong("total", totalStored.longValue());
|
|
||||||
} else {
|
|
||||||
tag.putString("total", totalStored.toString());
|
|
||||||
}
|
|
||||||
// 将当前已存储的不同物品种类数缓存到 CustomData(键名: "types"),用于客户端 tooltip 显示
|
|
||||||
int typesCount = this.getCellStoredMap().size();
|
|
||||||
tag.putInt("types", typesCount);
|
|
||||||
});
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
isPersisted = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 插入物品到存储单元
|
// 插入物品到存储单元
|
||||||
@Override
|
@Override
|
||||||
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
|
public long insert(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||||
// 数量为0或类型不匹配直接返回
|
// 数量为0或类型不匹配直接返回
|
||||||
if (amount == 0)
|
if (amount == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
// 不允许存储无限单元自身
|
|
||||||
if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem)
|
|
||||||
return 0;
|
|
||||||
// 如果没有UUID,生成UUID并初始化存储
|
|
||||||
if (!this.hasUUID()) {
|
|
||||||
UUID id = UUID.randomUUID();
|
|
||||||
try {
|
|
||||||
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID("uuid", id));
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
getStorageInstance().getOrCreateCell(getUUID());
|
|
||||||
// 确保 storedMap 初始化并从持久层加载数据
|
|
||||||
this.getCellStoredMap();
|
|
||||||
}
|
}
|
||||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
// 不允许存储无限单元自身
|
||||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
if (what instanceof AEItemKey itemKey &&
|
||||||
|
itemKey.getItem() instanceof InfinityBigIntegerCellItem &&
|
||||||
|
itemKey.get(DataComponents.CUSTOM_DATA) != null
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有 UUID,且服务器端存储管理器已就绪,则生成 UUID 并初始化存储
|
||||||
|
if (storageManager != null && !this.hasUUID()) {
|
||||||
|
// 取出自定义 NBT(如果没有就返回空)
|
||||||
|
CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
|
CompoundTag tag = data.copyTag();
|
||||||
|
|
||||||
|
// 生成新的 UUID 并写入
|
||||||
|
UUID newUUID = UUID.randomUUID();
|
||||||
|
tag.putUUID(InfinityConstants.INFINITY_CELL_UUID, newUUID);
|
||||||
|
|
||||||
|
// 回写到 ItemStack
|
||||||
|
self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
|
||||||
|
|
||||||
|
// 初始化存储
|
||||||
|
this.storageManager.getOrCreateCell(newUUID);
|
||||||
|
|
||||||
|
// 加载已存储的映射
|
||||||
|
loadCellStoredMap();
|
||||||
|
}
|
||||||
|
// 获取当前物品数量
|
||||||
|
BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
|
||||||
|
|
||||||
if (mode == Actionable.MODULATE) {
|
if (mode == Actionable.MODULATE) {
|
||||||
// 实际插入,更新数量并保存
|
// 实际插入,更新数量并保存
|
||||||
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
|
BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
|
||||||
map.put(what, newAmount);
|
getCellStoredMap().put(what, newAmount);
|
||||||
// 更新 cached total
|
|
||||||
totalStored = totalStored.add(BigInteger.valueOf(amount));
|
|
||||||
this.saveChanges();
|
this.saveChanges();
|
||||||
}
|
}
|
||||||
return amount;
|
return amount;
|
||||||
|
|
@ -393,35 +384,26 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||||
// 从存储单元提取物品
|
// 从存储单元提取物品
|
||||||
@Override
|
@Override
|
||||||
public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
|
public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
|
||||||
Object2ObjectMap<AEKey, BigInteger> map = this.getCellStoredMap();
|
BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
|
||||||
BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO);
|
// 如果有物品可提取
|
||||||
if (currentAmount.compareTo(BigInteger.ZERO) > 0) {
|
if (currentAmount.compareTo(BigInteger.ZERO) > 0) {
|
||||||
|
|
||||||
BigInteger requested = BigInteger.valueOf(amount);
|
BigInteger requested = BigInteger.valueOf(amount);
|
||||||
if (currentAmount.compareTo(requested) <= 0) {
|
|
||||||
// 提取全部
|
// 如果提取数量大于等于当前数量
|
||||||
long ret;
|
if (requested.compareTo(currentAmount) >= 0) {
|
||||||
if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
|
|
||||||
ret = Long.MAX_VALUE;
|
|
||||||
} else {
|
|
||||||
ret = currentAmount.longValue();
|
|
||||||
}
|
|
||||||
if (mode == Actionable.MODULATE) {
|
if (mode == Actionable.MODULATE) {
|
||||||
map.remove(what);
|
getCellStoredMap().remove(what);
|
||||||
// 更新 cached total
|
|
||||||
// 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数
|
|
||||||
totalStored = totalStored.subtract(currentAmount);
|
|
||||||
this.saveChanges();
|
this.saveChanges();
|
||||||
}
|
}
|
||||||
return ret;
|
return currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : currentAmount.longValue();
|
||||||
} else {
|
} else {
|
||||||
// 提取部分
|
// 提取部分数量
|
||||||
if (mode == Actionable.MODULATE) {
|
if (mode == Actionable.MODULATE) {
|
||||||
map.put(what, currentAmount.subtract(requested));
|
getCellStoredMap().put(what, currentAmount.subtract(requested));
|
||||||
// 更新 cached total
|
|
||||||
totalStored = totalStored.subtract(requested);
|
|
||||||
this.saveChanges();
|
this.saveChanges();
|
||||||
}
|
}
|
||||||
return amount;
|
return requested.longValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -430,6 +412,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell {
|
||||||
// 获取存储单元内所有物品的总数量(格式化字符串)
|
// 获取存储单元内所有物品的总数量(格式化字符串)
|
||||||
public String getTotalStorage() {
|
public String getTotalStorage() {
|
||||||
// 使用缓存的 totalStored,避免每次全表扫描
|
// 使用缓存的 totalStored,避免每次全表扫描
|
||||||
return formatBigInteger(totalStored);
|
return formatBigInteger(totalAEKey2Amounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package com.extendedae_plus.ae.definitions.upgrades;
|
||||||
|
|
||||||
|
import appeng.items.materials.UpgradeCardItem;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
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.item.component.CustomData;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.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(byte multiplier) {
|
||||||
|
ItemStack stack = new ItemStack(ModItems.ENTITY_SPEED_CARD.get());
|
||||||
|
CompoundTag tag = new CompoundTag();
|
||||||
|
tag.putByte(NBT_MULTIPLIER, multiplier);
|
||||||
|
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte readMultiplier(ItemStack stack) {
|
||||||
|
if (stack == null || stack.isEmpty()) return (byte) 1;
|
||||||
|
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
|
||||||
|
if (customData == null || !customData.copyTag().contains(NBT_MULTIPLIER)) return (byte)1;
|
||||||
|
return customData.copyTag().getByte(NBT_MULTIPLIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Component getName(@NotNull ItemStack stack) {
|
||||||
|
byte mult = readMultiplier(stack);
|
||||||
|
String key;
|
||||||
|
switch (mult) {
|
||||||
|
case (byte)2 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x2";
|
||||||
|
case (byte)4 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x4";
|
||||||
|
case (byte)8 -> key = "item." + com.extendedae_plus.ExtendedAEPlus.MODID + ".entity_speed_card.x8";
|
||||||
|
case (byte)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) {
|
||||||
|
byte mult = readMultiplier(stack);
|
||||||
|
int cap = 1;
|
||||||
|
switch (mult) {
|
||||||
|
case (byte)16 -> cap = 1024;
|
||||||
|
case (byte)8 -> cap = 256;
|
||||||
|
case (byte)4 -> cap = 64;
|
||||||
|
case (byte)2 -> cap = 8;
|
||||||
|
}
|
||||||
|
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, TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips) {
|
||||||
|
super.appendHoverText(stack, context, lines, advancedTooltips);
|
||||||
|
lines.addAll(this.getTooltipLines(stack));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.extendedae_plus.ae.items;
|
||||||
|
|
||||||
|
import appeng.items.materials.UpgradeCardItem;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.component.CustomData;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResultHolder;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 频道卡(MVP):仅存储一个 long 类型的频道号到 NBT:"channel"。
|
||||||
|
* 继承 AE2 的 UpgradeCardItem 以复用升级卡判定与提示框架。
|
||||||
|
*/
|
||||||
|
public class ChannelCardItem extends UpgradeCardItem {
|
||||||
|
public static final String TAG_CHANNEL = "channel";
|
||||||
|
|
||||||
|
public ChannelCardItem(Item.Properties properties) {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setChannel(ItemStack stack, long channel) {
|
||||||
|
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
|
||||||
|
tag.putLong(TAG_CHANNEL, channel);
|
||||||
|
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getChannel(ItemStack stack) {
|
||||||
|
CompoundTag tag = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
|
||||||
|
return tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag flag) {
|
||||||
|
super.appendHoverText(stack, context, lines, flag);
|
||||||
|
long ch = getChannel(stack);
|
||||||
|
if (ch == 0L) {
|
||||||
|
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset"));
|
||||||
|
} else {
|
||||||
|
lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||||
|
ItemStack stack = player.getItemInHand(hand);
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
long ch = getChannel(stack);
|
||||||
|
boolean dec = player.isShiftKeyDown();
|
||||||
|
long next = dec ? Math.max(0L, ch - 1L) : ch + 1L;
|
||||||
|
if (next != ch) {
|
||||||
|
setChannel(stack, next);
|
||||||
|
player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InteractionResultHolder.sidedSuccess(stack, level.isClientSide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.extendedae_plus.ae.items;
|
||||||
|
|
||||||
|
import appeng.items.parts.PartItem;
|
||||||
|
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class EntitySpeedTickerPartItem extends PartItem<EntitySpeedTickerPart> {
|
||||||
|
public EntitySpeedTickerPartItem(Properties properties) {
|
||||||
|
super(properties, EntitySpeedTickerPart.class, EntitySpeedTickerPart::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> tooltip, TooltipFlag tooltipFlag) {
|
||||||
|
super.appendHoverText(stack, context, tooltip, tooltipFlag);
|
||||||
|
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 网络能量,网络能量不足时无法加速"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,81 +1,88 @@
|
||||||
package com.extendedae_plus.ae.items;
|
package com.extendedae_plus.ae.items;
|
||||||
|
|
||||||
|
import appeng.api.config.FuzzyMode;
|
||||||
|
import appeng.api.storage.cells.ICellWorkbenchItem;
|
||||||
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory;
|
||||||
|
import com.extendedae_plus.util.storage.InfinityConstants;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.nbt.LongTag;
|
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.item.TooltipFlag;
|
import net.minecraft.world.item.TooltipFlag;
|
||||||
import net.minecraft.world.item.component.CustomData;
|
import net.minecraft.world.item.component.CustomData;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class InfinityBigIntegerCellItem extends Item {
|
public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem {
|
||||||
|
|
||||||
public InfinityBigIntegerCellItem() {
|
public InfinityBigIntegerCellItem() {
|
||||||
super(new Properties().stacksTo(1).fireResistant());
|
super(new Properties().stacksTo(1).fireResistant());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void appendHoverText(ItemStack stack,
|
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> tooltip, TooltipFlag tooltipFlag) {
|
||||||
@NotNull TooltipContext context,
|
|
||||||
List<Component> tooltip,
|
|
||||||
@NotNull TooltipFlag tooltipFlag) {
|
|
||||||
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
|
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1"));
|
||||||
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
|
tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2"));
|
||||||
|
|
||||||
Preconditions.checkArgument(stack.getItem() == this);
|
Preconditions.checkArgument(stack.getItem() == this);
|
||||||
CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
|
// 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
|
||||||
if (customData != null) {
|
CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
|
|
||||||
|
if (!customData.isEmpty()) {
|
||||||
CompoundTag tag = customData.copyTag();
|
CompoundTag tag = customData.copyTag();
|
||||||
|
|
||||||
if (tag != null && tag.contains("uuid")) {
|
if (tag.contains(InfinityConstants.INFINITY_CELL_UUID)) {
|
||||||
String uuidStr = tag.getUUID("uuid").toString();
|
String uuidStr = tag.getUUID(InfinityConstants.INFINITY_CELL_UUID).toString();
|
||||||
tooltip.add(
|
tooltip.add(
|
||||||
Component.literal("UUID: ")
|
Component.literal("UUID: ").withStyle(ChatFormatting.GRAY)
|
||||||
.withStyle(ChatFormatting.GRAY)
|
|
||||||
.append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
|
.append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (tag.contains("types")) {
|
if (tag.contains(InfinityConstants.INFINITY_ITEM_TYPES)) {
|
||||||
try {
|
try {
|
||||||
int types = tag.getInt("types");
|
int types = tag.getInt(InfinityConstants.INFINITY_ITEM_TYPES);
|
||||||
tooltip.add(
|
tooltip.add(
|
||||||
Component.literal("Types: ")
|
Component.literal("Types: ").withStyle(ChatFormatting.GRAY)
|
||||||
.withStyle(ChatFormatting.GRAY)
|
.append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
|
||||||
.append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN))
|
);
|
||||||
);
|
} catch (Exception ignored) {}
|
||||||
} catch (Exception ignored) {
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag.contains("total")) {
|
if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) {
|
||||||
BigInteger total = BigInteger.ZERO;
|
try {
|
||||||
Tag t = tag.get("total");
|
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL);
|
||||||
try {
|
BigInteger total = new BigInteger(bytes);
|
||||||
if (t instanceof LongTag) {
|
|
||||||
total = BigInteger.valueOf(tag.getLong("total"));
|
|
||||||
} else {
|
|
||||||
String s = tag.getString("total");
|
|
||||||
total = new BigInteger(s);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||||
tooltip.add(
|
tooltip.add(
|
||||||
Component.literal("Byte: ")
|
Component.literal("Total: ").withStyle(ChatFormatting.GRAY)
|
||||||
.withStyle(ChatFormatting.GRAY)
|
|
||||||
.append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
.append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||||
);
|
);
|
||||||
}
|
} catch (Exception ignored) {}
|
||||||
|
} else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) {
|
||||||
|
try {
|
||||||
|
byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
|
||||||
|
BigInteger total = new BigInteger(bytes);
|
||||||
|
String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
|
||||||
|
tooltip.add(
|
||||||
|
Component.literal("Total: ").withStyle(ChatFormatting.GRAY)
|
||||||
|
.append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
|
||||||
|
);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FuzzyMode getFuzzyMode(ItemStack itemStack) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFuzzyMode(ItemStack itemStack, FuzzyMode fuzzyMode) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
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.ModConfigs;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.init.ModMenuTypes;
|
||||||
|
import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils;
|
||||||
|
import com.extendedae_plus.util.entitySpeed.PowerUtils;
|
||||||
|
import it.unimi.dsi.fastutil.shorts.ShortSet;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.inventory.Slot;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体加速器菜单,负责管理客户端与服务端的数据同步,处理加速卡、能量卡和目标方块的状态。
|
||||||
|
*/
|
||||||
|
public class EntitySpeedTickerMenu extends UpgradeableMenu<EntitySpeedTickerPart> {
|
||||||
|
@GuiSync(716) public boolean accelerateEnabled = true; // 是否启用加速
|
||||||
|
@GuiSync(717) public int entitySpeedCardCount; // 已安装的实体加速卡数量
|
||||||
|
@GuiSync(718) public int energyCardCount; // 已安装的能量卡数量
|
||||||
|
@GuiSync(719) public int effectiveSpeed = 1; // 当前生效的加速倍率
|
||||||
|
@GuiSync(720) public double multiplier = 1.0; // 目标方块的配置倍率
|
||||||
|
@GuiSync(721) public boolean targetBlacklisted = false; // 目标方块是否在黑名单中
|
||||||
|
@GuiSync(722) public boolean networkEnergySufficient = true; // 网络能量是否充足
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数,初始化菜单并绑定部件。
|
||||||
|
* @param id 菜单ID
|
||||||
|
* @param ip 玩家背包
|
||||||
|
* @param host 关联的实体加速器部件
|
||||||
|
*/
|
||||||
|
public EntitySpeedTickerMenu(int id, Inventory ip, EntitySpeedTickerPart host) {
|
||||||
|
super(ModMenuTypes.ENTITY_TICKER_MENU.get(), id, ip, host);
|
||||||
|
if (host != null) {
|
||||||
|
host.menu = this; // 绑定菜单到部件
|
||||||
|
this.accelerateEnabled = host.getAccelerateEnabled(); // 同步初始开关状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加速开关状态。
|
||||||
|
* @return 是否启用加速
|
||||||
|
*/
|
||||||
|
public boolean getAccelerateEnabled() {
|
||||||
|
return this.accelerateEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置加速开关状态,并同步到部件。
|
||||||
|
* @param enabled 是否启用加速
|
||||||
|
*/
|
||||||
|
public void setAccelerateEnabled(boolean enabled) {
|
||||||
|
this.accelerateEnabled = enabled;
|
||||||
|
if (getHost() != null) {
|
||||||
|
getHost().setAccelerateEnabled(enabled); // 同步到部件
|
||||||
|
}
|
||||||
|
broadcastChanges(); // 广播状态变化
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新网络能量充足状态并广播到客户端。
|
||||||
|
* @param sufficient 是否能量充足
|
||||||
|
*/
|
||||||
|
public void setNetworkEnergySufficient(boolean sufficient) {
|
||||||
|
this.networkEnergySufficient = sufficient;
|
||||||
|
broadcastChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端数据同步到客户端时调用,更新卡数量、目标状态和生效速度。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onServerDataSync(ShortSet updatedFields) {
|
||||||
|
super.onServerDataSync(updatedFields);
|
||||||
|
updateCardCounts(); // 更新卡数量
|
||||||
|
updateTargetStatus(); // 更新目标方块的黑名单和倍率
|
||||||
|
updateEffectiveSpeed(); // 计算生效速度
|
||||||
|
updateNetworkEnergyStatus(); // 同步能量状态
|
||||||
|
if (isClientSide()) {
|
||||||
|
refreshClientGui(); // 客户端刷新界面
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当槽位内容变化时调用,客户端更新卡数量和生效速度。
|
||||||
|
* @param slot 发生变化的槽位
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onSlotChange(Slot slot) {
|
||||||
|
super.onSlotChange(slot);
|
||||||
|
if (isClientSide()) {
|
||||||
|
updateCardCounts();
|
||||||
|
updateEffectiveSpeed();
|
||||||
|
refreshClientGui();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广播数据变化,清理未启用槽位的显示堆栈。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void broadcastChanges() {
|
||||||
|
for (Object o : this.slots) {
|
||||||
|
if (o instanceof OptionalFakeSlot fs && !fs.isSlotEnabled() && !fs.getDisplayStack().isEmpty()) {
|
||||||
|
fs.clearStack(); // 清理未启用槽位的显示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
standardDetectAndSendChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新加速卡和能量卡的数量。
|
||||||
|
*/
|
||||||
|
private void updateCardCounts() {
|
||||||
|
this.entitySpeedCardCount = this.getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
|
||||||
|
this.energyCardCount = this.getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新目标方块的黑名单状态和倍率。
|
||||||
|
*/
|
||||||
|
private void updateTargetStatus() {
|
||||||
|
BlockEntity target = getTargetBlockEntity();
|
||||||
|
if (target == null) {
|
||||||
|
this.multiplier = 1.0;
|
||||||
|
this.targetBlacklisted = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String blockId = net.minecraft.core.registries.BuiltInRegistries.BLOCK.getKey(target.getBlockState().getBlock()).toString();
|
||||||
|
this.multiplier = ConfigParsingUtils.getMultiplierForBlock(blockId, ModConfigs.ENTITY_TICKER_MULTIPLIERS.get());
|
||||||
|
this.targetBlacklisted = ConfigParsingUtils.isBlockBlacklisted(blockId, ModConfigs.ENTITY_TICKER_BLACK_LIST.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算生效速度(考虑黑名单和卡数量)。
|
||||||
|
*/
|
||||||
|
private void updateEffectiveSpeed() {
|
||||||
|
this.effectiveSpeed = targetBlacklisted ? 0 : (int) PowerUtils.computeProductWithCap(getUpgrades(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步网络能量状态(仅服务端)。
|
||||||
|
*/
|
||||||
|
private void updateNetworkEnergyStatus() {
|
||||||
|
if (!isClientSide() && getHost() != null) {
|
||||||
|
this.networkEnergySufficient = getHost().isNetworkEnergySufficient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端刷新界面。
|
||||||
|
*/
|
||||||
|
private void refreshClientGui() {
|
||||||
|
if (Minecraft.getInstance().screen instanceof EntitySpeedTickerScreen screen) {
|
||||||
|
screen.refreshGui();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标方块实体。
|
||||||
|
* @return 目标方块实体或 null
|
||||||
|
*/
|
||||||
|
private BlockEntity getTargetBlockEntity() {
|
||||||
|
return getHost() != null ?
|
||||||
|
getHost().getLevel().getBlockEntity(
|
||||||
|
getHost().getBlockEntity().getBlockPos().relative(getHost().getSide())
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,398 @@
|
||||||
|
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.energy.IEnergyService;
|
||||||
|
import appeng.api.networking.security.IActionSource;
|
||||||
|
import appeng.api.networking.ticking.IGridTickable;
|
||||||
|
import appeng.api.networking.ticking.TickRateModulation;
|
||||||
|
import appeng.api.networking.ticking.TickingRequest;
|
||||||
|
import appeng.api.parts.IPartCollisionHelper;
|
||||||
|
import appeng.api.parts.IPartItem;
|
||||||
|
import appeng.api.parts.IPartModel;
|
||||||
|
import appeng.api.storage.MEStorage;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
import appeng.core.definitions.AEItems;
|
||||||
|
import appeng.items.parts.PartModels;
|
||||||
|
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.ModConfigs;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.init.ModMenuTypes;
|
||||||
|
import com.extendedae_plus.util.entitySpeed.ConfigParsingUtils;
|
||||||
|
import com.extendedae_plus.util.entitySpeed.PowerUtils;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
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.neoforged.fml.ModList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static final ResourceLocation MODEL_BASE = ResourceLocation.fromNamespaceAndPath(
|
||||||
|
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, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_off"));
|
||||||
|
MODELS_ON = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_on"));
|
||||||
|
MODELS_HAS_CHANNEL = new PartModel(MODEL_BASE, ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "part/entity_speed_ticker_has_channel"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntitySpeedTickerMenu menu; // 当前打开的菜单实例
|
||||||
|
private boolean accelerateEnabled = true; // 是否启用加速
|
||||||
|
private boolean networkEnergySufficient = true; // 网络能量是否充足
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数,初始化部件并设置网络节点属性。
|
||||||
|
* @param partItem 部件物品
|
||||||
|
*/
|
||||||
|
public EntitySpeedTickerPart(IPartItem<?> partItem) {
|
||||||
|
super(partItem);
|
||||||
|
this.getMainNode()
|
||||||
|
.setFlags(GridFlags.REQUIRE_CHANNEL)
|
||||||
|
.setIdlePowerUsage(1)
|
||||||
|
.addService(IGridTickable.class, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAccelerateEnabled() {
|
||||||
|
return this.accelerateEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNetworkEnergySufficient() {
|
||||||
|
return this.networkEnergySufficient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置加速开关状态并通知菜单。
|
||||||
|
* @param enabled 是否启用加速
|
||||||
|
*/
|
||||||
|
public void setAccelerateEnabled(boolean enabled) {
|
||||||
|
this.accelerateEnabled = enabled;
|
||||||
|
if (menu != null) {
|
||||||
|
menu.setAccelerateEnabled(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新网络能量充足状态并通知菜单。
|
||||||
|
* @param sufficient 是否能量充足
|
||||||
|
*/
|
||||||
|
private void updateNetworkEnergySufficient(boolean sufficient) {
|
||||||
|
this.networkEnergySufficient = sufficient;
|
||||||
|
if (menu != null) {
|
||||||
|
menu.setNetworkEnergySufficient(sufficient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前状态的渲染模型。
|
||||||
|
* @return 当前状态的模型
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IPartModel getStaticModels() {
|
||||||
|
if (this.isActive() && this.isPowered()) {
|
||||||
|
return MODELS_HAS_CHANNEL;
|
||||||
|
} else if (this.isPowered()) {
|
||||||
|
return MODELS_ON;
|
||||||
|
} else {
|
||||||
|
return MODELS_OFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当玩家激活部件(右键)时调用,打开自定义菜单
|
||||||
|
*
|
||||||
|
* @param player 玩家
|
||||||
|
* @param pos 点击位置
|
||||||
|
* @return 总是返回 true,表示激活成功
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final boolean onUseWithoutItem(Player player, Vec3 pos) {
|
||||||
|
if (!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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当升级卡数量发生变化时调用,通知菜单更新
|
||||||
|
*/
|
||||||
|
@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 目标方块实体
|
||||||
|
* @param <T> 方块实体类型
|
||||||
|
*/
|
||||||
|
private <T extends BlockEntity> void ticker(@NotNull T blockEntity) {
|
||||||
|
if (!isValidForTicking()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String blockId = BuiltInRegistries.BLOCK.getKey(blockEntity.getBlockState().getBlock()).toString();
|
||||||
|
if (ConfigParsingUtils.isBlockBlacklisted(blockId, ModConfigs.ENTITY_TICKER_BLACK_LIST.get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockEntityTicker<T> ticker = getTicker(blockEntity);
|
||||||
|
if (ticker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int speed = calculateSpeed();
|
||||||
|
if (speed <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double requiredPower = calculateRequiredPower(speed, blockId);
|
||||||
|
if (!extractPower(requiredPower)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performTicks(blockEntity, ticker, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查网络节点是否有效。
|
||||||
|
* @return 是否可以执行 tick
|
||||||
|
*/
|
||||||
|
private boolean isValidForTicking() {
|
||||||
|
return getGridNode() != null && getMainNode() != null && getMainNode().getGrid() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取目标方块实体的 ticker。
|
||||||
|
* @param blockEntity 目标方块实体
|
||||||
|
* @return ticker 或 null
|
||||||
|
*/
|
||||||
|
private <T extends BlockEntity> BlockEntityTicker<T> getTicker(T blockEntity) {
|
||||||
|
return getLevel().getBlockState(blockEntity.getBlockPos())
|
||||||
|
.getTicker(getLevel(), (BlockEntityType<T>) blockEntity.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算加速倍率。
|
||||||
|
* @return 生效的加速倍率
|
||||||
|
*/
|
||||||
|
private int calculateSpeed() {
|
||||||
|
int entitySpeedCardCount = getUpgrades().getInstalledUpgrades(ModItems.ENTITY_SPEED_CARD.get());
|
||||||
|
if (entitySpeedCardCount <= 0) return 0;
|
||||||
|
return (int) PowerUtils.computeProductWithCap(getUpgrades(), 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算所需能量。
|
||||||
|
* @param speed 加速倍率
|
||||||
|
* @param blockId 目标方块ID
|
||||||
|
* @return 所需能量
|
||||||
|
*/
|
||||||
|
private double calculateRequiredPower(int speed, String blockId) {
|
||||||
|
int energyCardCount = getUpgrades().getInstalledUpgrades(AEItems.ENERGY_CARD);
|
||||||
|
double multiplier = ConfigParsingUtils.getMultiplierForBlock(blockId, ModConfigs.ENTITY_TICKER_MULTIPLIERS.get());
|
||||||
|
return PowerUtils.computeFinalPowerForProduct(speed, energyCardCount) * multiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取网络能量并更新状态,优先从 AE2 网络提取 AE 能量,不足时从磁盘提取 FE 能量。
|
||||||
|
* @param requiredPower 所需能量(AE 单位)
|
||||||
|
* @return 是否成功提取足够能量
|
||||||
|
*/
|
||||||
|
private boolean extractPower(double requiredPower) {
|
||||||
|
IEnergyService energyService = getMainNode().getGrid().getEnergyService();
|
||||||
|
MEStorage storage = getMainNode().getGrid().getStorageService().getInventory();
|
||||||
|
IActionSource source = IActionSource.ofMachine(this);
|
||||||
|
boolean appFluxLoaded = ModList.get().isLoaded("appflux");
|
||||||
|
boolean preferDiskEnergy = appFluxLoaded && ModConfigs.PRIORITIZE_DISK_ENERGY.get();
|
||||||
|
|
||||||
|
// 如果 appflux 存在且优先磁盘能量,尝试提取 FE 能量
|
||||||
|
if (appFluxLoaded && preferDiskEnergy) {
|
||||||
|
if (tryExtractFE(energyService, storage, requiredPower, source)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试提取 AE 能量(当 appflux 不存在、优先 AE 能量或 FE 提取失败时)
|
||||||
|
double simulated = energyService.extractAEPower(requiredPower, Actionable.SIMULATE, PowerMultiplier.CONFIG);
|
||||||
|
if (simulated >= requiredPower) {
|
||||||
|
double extracted = energyService.extractAEPower(requiredPower, Actionable.MODULATE, PowerMultiplier.CONFIG);
|
||||||
|
boolean sufficient = extracted >= requiredPower;
|
||||||
|
updateNetworkEnergySufficient(sufficient);
|
||||||
|
return sufficient;
|
||||||
|
}
|
||||||
|
updateNetworkEnergySufficient(false);
|
||||||
|
|
||||||
|
// 如果 appflux 存在且优先 AE 能量,尝试提取 FE 能量作为备用
|
||||||
|
if (appFluxLoaded && !preferDiskEnergy) {
|
||||||
|
return tryExtractFE(energyService, storage, requiredPower, source);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tryExtractFE(IEnergyService energyService, MEStorage storage, double requiredPower, IActionSource source) {
|
||||||
|
try {
|
||||||
|
Class<?> helperClass = Class.forName("com.extendedae_plus.util.FluxEnergyHelper");
|
||||||
|
Method extractMethod = helperClass.getMethod(
|
||||||
|
"extractFE",
|
||||||
|
IEnergyService.class,
|
||||||
|
MEStorage.class,
|
||||||
|
long.class,
|
||||||
|
IActionSource.class
|
||||||
|
);
|
||||||
|
long feRequired = (long) requiredPower << 1; // 1 AE = 2 FE
|
||||||
|
long feExtracted = (long) extractMethod.invoke(null, energyService, storage, feRequired, source);
|
||||||
|
if (feExtracted >= feRequired) {
|
||||||
|
updateNetworkEnergySufficient(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果反射失败,视为 FE 不可用
|
||||||
|
}
|
||||||
|
updateNetworkEnergySufficient(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行加速 tick 操作。
|
||||||
|
* @param blockEntity 目标方块实体
|
||||||
|
* @param ticker 方块实体 ticker
|
||||||
|
* @param speed 加速倍率
|
||||||
|
*/
|
||||||
|
private <T extends BlockEntity> void performTicks(T blockEntity,
|
||||||
|
BlockEntityTicker<T> ticker,
|
||||||
|
int speed) {
|
||||||
|
// 执行 speed-1 次额外 tick(原生 tick 已包含 1 次)
|
||||||
|
for (int i = 0; i < speed - 1; i++) {
|
||||||
|
ticker.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,119 @@
|
||||||
|
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.network.ToggleEntityTickerC2SPacket;
|
||||||
|
import com.extendedae_plus.util.entitySpeed.PowerUtils;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EntitySpeedTickerScreen<C extends EntitySpeedTickerMenu> extends UpgradeableScreen<C> {
|
||||||
|
private boolean eap$entitySpeedTickerEnabled = false; // 本地缓存的加速开关状态
|
||||||
|
private final SettingToggleButton<YesNo> eap$entitySpeedTickerToggle; // 加速开关按钮
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数,初始化界面和控件。
|
||||||
|
* @param menu 实体加速器菜单
|
||||||
|
* @param playerInventory 玩家背包
|
||||||
|
* @param title 界面标题
|
||||||
|
* @param style 界面样式
|
||||||
|
*/
|
||||||
|
public EntitySpeedTickerScreen(EntitySpeedTickerMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||||
|
super((C) menu, playerInventory, title, style);
|
||||||
|
this.addToLeftToolbar(CommonButtons.togglePowerUnit()); // 添加功率单位切换按钮
|
||||||
|
this.eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
|
||||||
|
|
||||||
|
// 初始化加速开关按钮
|
||||||
|
eap$entitySpeedTickerToggle = new SettingToggleButton<>(
|
||||||
|
Settings.BLOCKING_MODE,
|
||||||
|
this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO,
|
||||||
|
(btn, backwards) -> {
|
||||||
|
// 不做本地切换,点击仅发送自定义C2S,显示由@GuiSync回传
|
||||||
|
var conn = Minecraft.getInstance().getConnection();
|
||||||
|
if (conn != null) conn.send(ToggleEntityTickerC2SPacket.INSTANCE);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@Override
|
||||||
|
public List<Component> getTooltipMessage() {
|
||||||
|
if (menu.targetBlacklisted) {
|
||||||
|
return List.of(
|
||||||
|
Component.literal("实体加速"),
|
||||||
|
Component.literal("已禁用(目标在黑名单)")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
boolean enabled = eap$entitySpeedTickerEnabled;
|
||||||
|
return List.of(
|
||||||
|
Component.literal("实体加速"),
|
||||||
|
enabled ? Component.literal("已启用: 将加速目标方块实体的tick") :
|
||||||
|
Component.literal("已关闭: 不会对目标方块实体进行加速")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Icon getIcon() {
|
||||||
|
if (menu.targetBlacklisted) return Icon.INVALID;
|
||||||
|
return this.getCurrentValue() == YesNo.YES ? Icon.VALID : Icon.INVALID;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eap$entitySpeedTickerToggle.set(this.eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO);
|
||||||
|
this.addToLeftToolbar(eap$entitySpeedTickerToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateBeforeRender() {
|
||||||
|
super.updateBeforeRender();
|
||||||
|
if (eap$entitySpeedTickerToggle != null && menu != null) {
|
||||||
|
eap$entitySpeedTickerEnabled = menu.getAccelerateEnabled();
|
||||||
|
// 如果目标在黑名单,禁用按钮并显示关闭状态
|
||||||
|
eap$entitySpeedTickerToggle.set(menu.targetBlacklisted ? YesNo.NO : (eap$entitySpeedTickerEnabled ? YesNo.YES : YesNo.NO));
|
||||||
|
eap$entitySpeedTickerToggle.active = !menu.targetBlacklisted;
|
||||||
|
}
|
||||||
|
textData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshGui() {
|
||||||
|
textData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新界面文本内容,包括加速状态、速度、能耗和倍率。
|
||||||
|
*/
|
||||||
|
private void textData() {
|
||||||
|
Map<String, Component> textContents = new HashMap<>();
|
||||||
|
if (getMenu().targetBlacklisted) {
|
||||||
|
// 黑名单禁用时的默认显示
|
||||||
|
textContents.put("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.enable"));
|
||||||
|
textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", 0));
|
||||||
|
textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(0.0, false)));
|
||||||
|
textContents.put("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(0.0)));
|
||||||
|
textContents.put("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", 0.0)));
|
||||||
|
} else {
|
||||||
|
// 正常状态下显示实际数据
|
||||||
|
int energyCardCount = getMenu().energyCardCount;
|
||||||
|
double multiplier = getMenu().multiplier;
|
||||||
|
int effectiveSpeed = getMenu().effectiveSpeed;
|
||||||
|
double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount);
|
||||||
|
double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount);
|
||||||
|
|
||||||
|
textContents.put("enable", getMenu().networkEnergySufficient ? null :
|
||||||
|
Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient"));
|
||||||
|
textContents.put("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed));
|
||||||
|
textContents.put("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false)));
|
||||||
|
textContents.put("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio)));
|
||||||
|
textContents.put("multiplier", Component.translatable("screen.extendedae_plus.entity_speed_ticker.multiplier", String.format("%.2fx", multiplier)));
|
||||||
|
}
|
||||||
|
textContents.forEach(this::setTextContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.extendedae_plus.bridge;
|
||||||
|
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅用于我方在未安装 appflux 时向逻辑类暴露自带升级槽,避免与 appflux 的 IUpgradeableObject 冲突。
|
||||||
|
*/
|
||||||
|
public interface CompatUpgradeProvider {
|
||||||
|
IUpgradeInventory eap$getCompatUpgrades();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.extendedae_plus.bridge;
|
||||||
|
|
||||||
|
import appeng.menu.ToolboxMenu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提供给 AE2 菜单以暴露升级槽与工具箱面板的接口。
|
||||||
|
* PatternProviderMenu 的 mixin 会在未安装 appflux 时实现该接口,
|
||||||
|
* 以便在界面上显示升级卡槽(用于放置频道卡)。
|
||||||
|
*/
|
||||||
|
public interface IUpgradableMenu {
|
||||||
|
ToolboxMenu eap$getToolbox();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.extendedae_plus.bridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 非 mixin 包下的桥接接口,供 mixin 进行 instanceof 检测和回调。
|
||||||
|
*/
|
||||||
|
public interface InterfaceWirelessLinkBridge {
|
||||||
|
void eap$updateWirelessLink();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取无线连接状态(服务端返回真实状态,客户端返回同步状态)
|
||||||
|
*/
|
||||||
|
default boolean eap$isWirelessConnected() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置客户端的无线连接状态(仅在客户端使用)
|
||||||
|
*/
|
||||||
|
default void eap$setClientWirelessState(boolean connected) {
|
||||||
|
// 默认实现为空
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否已经进行过tick初始化
|
||||||
|
*/
|
||||||
|
default boolean eap$hasTickInitialized() {
|
||||||
|
return true; // 默认认为已初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置tick初始化状态
|
||||||
|
*/
|
||||||
|
default void eap$setTickInitialized(boolean initialized) {
|
||||||
|
// 默认实现为空
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行频道链接初始化
|
||||||
|
*/
|
||||||
|
default void eap$initializeChannelLink() {
|
||||||
|
// 默认实现为空
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查并处理延迟初始化
|
||||||
|
*/
|
||||||
|
default void eap$handleDelayedInit() {
|
||||||
|
// 默认实现为空
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指示是否需要保持慢速tick(用于轮询频道卡或维持无线状态)
|
||||||
|
*/
|
||||||
|
default boolean eap$shouldKeepTicking() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
package com.extendedae_plus.client;
|
package com.extendedae_plus.client;
|
||||||
|
|
||||||
import appeng.client.render.crafting.CraftingCubeModel;
|
import appeng.client.render.crafting.CraftingCubeModel;
|
||||||
|
import appeng.init.client.InitScreens;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
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.render.crafting.EPlusCraftingCubeModelProvider;
|
||||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||||
import com.extendedae_plus.hooks.BuiltInModelHooks;
|
import com.extendedae_plus.hooks.BuiltInModelHooks;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
import com.extendedae_plus.init.ModMenuTypes;
|
import com.extendedae_plus.init.ModMenuTypes;
|
||||||
|
import net.minecraft.client.renderer.item.ItemProperties;
|
||||||
import net.neoforged.api.distmarker.Dist;
|
import net.neoforged.api.distmarker.Dist;
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
|
||||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,6 +29,10 @@ public final class ClientProxy {
|
||||||
public static void init() {
|
public static void init() {
|
||||||
if (REGISTERED) return;
|
if (REGISTERED) return;
|
||||||
REGISTERED = true;
|
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(
|
BuiltInModelHooks.addBuiltInModel(
|
||||||
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
|
ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"),
|
||||||
|
|
@ -44,16 +53,7 @@ public final class ClientProxy {
|
||||||
BuiltInModelHooks.addBuiltInModel(
|
BuiltInModelHooks.addBuiltInModel(
|
||||||
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
|
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
|
||||||
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
|
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。
|
|
||||||
*/
|
|
||||||
public static void onClientSetup(final FMLClientSetupEvent event) {
|
|
||||||
event.enqueueWork(() -> {
|
|
||||||
// 确保在首次资源加载前完成内置模型注册(REGISTERED 保护避免重复)
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|
@ -73,5 +73,10 @@ public final class ClientProxy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册由 AE2 InitScreens 所需的屏幕资源映射(用于内置 JSON 屏幕注册)
|
||||||
|
*/
|
||||||
|
InitScreens.register(event, ModMenuTypes.ENTITY_TICKER_MENU.get(), EntitySpeedTickerScreen<EntitySpeedTickerMenu>::new, "/screens/entity_speed_ticker.json");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,8 @@ import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT)
|
|
||||||
public final class InputEvents {
|
public final class InputEvents {
|
||||||
private InputEvents() {}
|
private InputEvents() {}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onMouseButtonPre(InputEvent.MouseButton.Pre event) {
|
public static void onMouseButtonPre(InputEvent.MouseButton.Pre event) {
|
||||||
|
|
@ -47,13 +46,10 @@ public final class InputEvents {
|
||||||
GenericStack stack = stacks.isEmpty() ? null : stacks.getFirst();
|
GenericStack stack = stacks.isEmpty() ? null : stacks.getFirst();
|
||||||
if (stack == null) return;
|
if (stack == null) return;
|
||||||
|
|
||||||
// 发送到服务端,让其验证并打开 CraftAmountMenu
|
PacketDistributor.sendToServer(new OpenCraftFromJeiC2SPacket(stack));
|
||||||
PacketDistributor.sendToServer(new OpenCraftFromJeiC2SPacket(stack));
|
event.setCanceled(true);
|
||||||
|
}
|
||||||
// 消费此次点击,避免 JEI/原版对中键的其它处理
|
}
|
||||||
event.setCanceled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onKeyPressedPre(ScreenEvent.KeyPressed.Pre event) {
|
public static void onKeyPressedPre(ScreenEvent.KeyPressed.Pre event) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.extendedae_plus.compat;
|
||||||
|
|
||||||
|
import appeng.client.gui.implementations.PatternProviderScreen;
|
||||||
|
import appeng.client.gui.widgets.UpgradesPanel;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppliedFlux 兼容性处理工具类
|
||||||
|
* 用于检测和处理与AppliedFlux模组的UI冲突
|
||||||
|
*/
|
||||||
|
public final class AppliedFluxCompat {
|
||||||
|
|
||||||
|
private AppliedFluxCompat() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查PatternProviderScreen是否已经有AppliedFlux添加的升级面板
|
||||||
|
* 简化版本:主要通过AppliedFlux模组加载状态来判断
|
||||||
|
*/
|
||||||
|
public static boolean hasAppliedFluxUpgradePanel(PatternProviderScreen<?> screen) {
|
||||||
|
// 如果AppliedFlux未加载,肯定没有其升级面板
|
||||||
|
if (!UpgradeSlotCompat.shouldUseLowPriorityMode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 AppliedFlux 加载了,假设它会添加升级面板
|
||||||
|
// 这是一个保守的假设,避免冲突
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否应该跳过添加我们的升级面板
|
||||||
|
* 主要用于检测 AppliedFlux 是否已经添加了升级面板
|
||||||
|
*/
|
||||||
|
public static boolean shouldSkipOurUpgradePanel(PatternProviderScreen<?> screen) {
|
||||||
|
if (!UpgradeSlotCompat.shouldUseLowPriorityMode()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAppliedFluxUpgradePanel(screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.extendedae_plus.compat;
|
||||||
|
|
||||||
|
import net.neoforged.fml.loading.FMLLoader;
|
||||||
|
import net.neoforged.fml.ModList;
|
||||||
|
|
||||||
|
public final class UpgradeSlotCompat {
|
||||||
|
private static Boolean APPFLUX_LOADED;
|
||||||
|
private static Boolean APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE;
|
||||||
|
|
||||||
|
private UpgradeSlotCompat() {}
|
||||||
|
|
||||||
|
private static boolean isAppfluxLoaded() {
|
||||||
|
if (APPFLUX_LOADED == null) {
|
||||||
|
try {
|
||||||
|
APPFLUX_LOADED = ModList.get().isLoaded("appflux");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// 早期阶段或运行环境差异
|
||||||
|
APPFLUX_LOADED = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APPFLUX_LOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测AppliedFlux的PatternProviderScreen mixin是否活跃
|
||||||
|
* 这比简单检查模组是否加载更准确
|
||||||
|
*/
|
||||||
|
private static boolean isAppfluxPatternProviderMixinActive() {
|
||||||
|
if (APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE == null) {
|
||||||
|
try {
|
||||||
|
if (!isAppfluxLoaded()) {
|
||||||
|
APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE = false;
|
||||||
|
} else {
|
||||||
|
// 尝试检测AppliedFlux的mixin类是否存在
|
||||||
|
Class.forName("com.glodblock.github.appflux.mixins.MixinPatternProviderScreen");
|
||||||
|
APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE = true;
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE = false;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APPFLUX_PATTERN_PROVIDER_MIXIN_ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否由我们提供升级槽(当未安装 appflux 时)
|
||||||
|
public static boolean shouldEnableUpgradeSlots() {
|
||||||
|
return !isAppfluxLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否启用频道卡支持(两种情况下都启用)
|
||||||
|
public static boolean shouldEnableChannelCard() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端界面是否需要显示升级面板
|
||||||
|
// 如果AppliedFlux的mixin活跃,我们降低优先级让它处理
|
||||||
|
public static boolean shouldAddUpgradePanelToScreen() {
|
||||||
|
return true; // 总是尝试添加,但在代码中检测冲突
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否应该使用低优先级模式(当AppliedFlux存在时)
|
||||||
|
public static boolean shouldUseLowPriorityMode() {
|
||||||
|
return isAppfluxPatternProviderMixinActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取推荐的mixin优先级
|
||||||
|
public static int getRecommendedMixinPriority() {
|
||||||
|
return isAppfluxPatternProviderMixinActive() ? 1500 : 2000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,96 +4,145 @@ import com.extendedae_plus.util.GetKey;
|
||||||
import net.neoforged.neoforge.common.ModConfigSpec;
|
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||||
|
|
||||||
public final class ModConfig {
|
public final class ModConfig {
|
||||||
|
// Common 配置
|
||||||
public static final ModConfigSpec COMMON_SPEC;
|
public static final ModConfigSpec COMMON_SPEC;
|
||||||
public static final ModConfigSpec.IntValue PAGE_MULTIPLIER;
|
public static final ModConfigSpec.IntValue PAGE_MULTIPLIER;
|
||||||
public static final ModConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
|
|
||||||
public static final ModConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
|
|
||||||
public static final ModConfigSpec.BooleanValue SHOW_ENCODE_PATTERN_PLAYER;
|
|
||||||
public static final ModConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
|
|
||||||
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
|
|
||||||
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
|
|
||||||
public static final ModConfigSpec.BooleanValue OVERRIDE_AE2WT_PICKING;
|
public static final ModConfigSpec.BooleanValue OVERRIDE_AE2WT_PICKING;
|
||||||
|
|
||||||
|
// Client 配置
|
||||||
|
public static final ModConfigSpec CLIENT_SPEC;
|
||||||
|
public static final ModConfigSpec.BooleanValue SHOW_ENCODER_PATTERN_PLAYER;
|
||||||
|
public static final ModConfigSpec.BooleanValue PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT;
|
||||||
|
public static final ModConfigSpec.BooleanValue PRIORITIZE_DISK_ENERGY;
|
||||||
public static final ModConfigSpec.BooleanValue INDEPENDENT_UPLOADING_BUTTON;
|
public static final ModConfigSpec.BooleanValue INDEPENDENT_UPLOADING_BUTTON;
|
||||||
|
|
||||||
static {
|
// Server 配置
|
||||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
public static final ModConfigSpec SERVER_SPEC;
|
||||||
|
public static final ModConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
|
||||||
|
public static final ModConfigSpec.IntValue SMART_SCALING_MAX_MULTIPLIER;
|
||||||
|
public static final ModConfigSpec.IntValue CRAFTING_PAUSE_THRESHOLD;
|
||||||
|
public static final ModConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
|
||||||
|
public static final ModConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
|
||||||
|
public static final ModConfigSpec.IntValue ENTITY_TICKER_COST;
|
||||||
|
public static final ModConfigSpec.ConfigValue<List<? extends String>> ENTITY_TICKER_BLACK_LIST;
|
||||||
|
public static final ModConfigSpec.ConfigValue<List<? extends String>> ENTITY_TICKER_MULTIPLIERS;
|
||||||
|
|
||||||
// General settings
|
static {
|
||||||
builder.push("general");
|
// Common 配置
|
||||||
PAGE_MULTIPLIER = builder.comment(
|
ModConfigSpec.Builder commonBuilder = new ModConfigSpec.Builder();
|
||||||
|
commonBuilder.push("common");
|
||||||
|
PAGE_MULTIPLIER = commonBuilder
|
||||||
|
.comment(
|
||||||
"扩展样板供应器总槽位容量的倍率",
|
"扩展样板供应器总槽位容量的倍率",
|
||||||
"基础为36,每页仍显示36格,倍率会增加总页数/总容量",
|
"基础为36,每页仍显示36格,倍率会增加总页数/总容量",
|
||||||
"建议范围 1-16")
|
"建议范围 1-16"
|
||||||
|
)
|
||||||
.defineInRange("pageMultiplier", 1, 1, 64);
|
.defineInRange("pageMultiplier", 1, 1, 64);
|
||||||
|
OVERRIDE_AE2WT_PICKING = commonBuilder
|
||||||
// 是否显示样板编码玩家(通用)
|
|
||||||
SHOW_ENCODE_PATTERN_PLAYER = builder.comment(
|
|
||||||
"是否显示样板编码玩家",
|
|
||||||
"开启后将在样板 HoverText 上添加样板的编码玩家")
|
|
||||||
.define("showEncoderPatternPlayer", true);
|
|
||||||
|
|
||||||
// 模式访问终端(ExtendedAE 图样终端)默认是否显示槽位渲染(SlotsRow)。
|
|
||||||
// true: 默认显示(可通过界面按钮临时隐藏);false: 默认隐藏(可通过按钮显示)
|
|
||||||
PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT = builder.comment(
|
|
||||||
"样板终端默认是否显示槽位",
|
|
||||||
"影响进入界面时SlotsRow的默认可见性,仅影响客户端显示")
|
|
||||||
.define("patternTerminalShowSlotsDefault", true);
|
|
||||||
|
|
||||||
OVERRIDE_AE2WT_PICKING = builder
|
|
||||||
.translation(new GetKey(GetKey.CONFIG)
|
.translation(new GetKey(GetKey.CONFIG)
|
||||||
.addStr("override_ae2wt_picking").buildRaw())
|
.addStr("override_ae2wt_picking").buildRaw())
|
||||||
.comment("是否覆盖AE2WT使用中键从终端选取方块的逻辑",
|
.comment("是否覆盖AE2WT使用中键从终端选取方块的逻辑",
|
||||||
"开启后选取方块的数量将不被限制在32个",
|
"开启后选取方块的数量将不被限制在32个",
|
||||||
"(不建议使用,在EMI环境下可能出现奇怪bug)")
|
"(不建议使用,在EMI环境下可能出现奇怪bug)")
|
||||||
.define("overrideAE2WTPicking", false);
|
.define("overrideAE2WTPicking", false);
|
||||||
|
commonBuilder.pop();
|
||||||
|
COMMON_SPEC = commonBuilder.build();
|
||||||
|
|
||||||
|
// Client 配置
|
||||||
|
ModConfigSpec.Builder clientBuilder = new ModConfigSpec.Builder();
|
||||||
|
clientBuilder.push("client");
|
||||||
|
SHOW_ENCODER_PATTERN_PLAYER = clientBuilder
|
||||||
|
.comment(
|
||||||
|
"是否显示样板编码玩家",
|
||||||
|
"开启后将在样板 HoverText 上添加样板的编码玩家")
|
||||||
|
.define("showEncoderPatternPlayer", true);
|
||||||
|
PATTERN_TERMINAL_SHOW_SLOTS_DEFAULT = clientBuilder
|
||||||
|
.comment(
|
||||||
|
"样板终端默认是否显示槽位",
|
||||||
|
"影响进入界面时SlotsRow的默认可见性,仅影响客户端显示")
|
||||||
|
.define("patternTerminalShowSlotsDefault", true);
|
||||||
INDEPENDENT_UPLOADING_BUTTON = builder
|
INDEPENDENT_UPLOADING_BUTTON = builder
|
||||||
.translation(new GetKey(GetKey.CONFIG)
|
.translation(new GetKey(GetKey.CONFIG)
|
||||||
.addStr("independence_uploading_button").buildRaw())
|
.addStr("independence_uploading_button").buildRaw())
|
||||||
.comment("启用后,在样板编码终端会出现一个独立的按钮用于上传样板")
|
.comment("启用后,在样板编码终端会出现一个独立的按钮用于上传样板")
|
||||||
.define("independentUploadingButton", false);
|
.define("independentUploadingButton", false);
|
||||||
|
clientBuilder.pop();
|
||||||
|
CLIENT_SPEC = clientBuilder.build();
|
||||||
|
|
||||||
// end general
|
// Server 配置
|
||||||
builder.pop();
|
ModConfigSpec.Builder serverBuilder = new ModConfigSpec.Builder();
|
||||||
|
serverBuilder.push("ae");
|
||||||
// Smart-scaling group
|
PROVIDER_ROUND_ROBIN_ENABLE = serverBuilder
|
||||||
builder.push("smartScaling");
|
.comment(
|
||||||
// 智能倍增:是否在样板供应器间轮询分配请求量(开启:按 provider 均分;关闭:不拆分)
|
|
||||||
PROVIDER_ROUND_ROBIN_ENABLE = builder.comment(
|
|
||||||
"智能倍增时是否对样板供应器轮询分配",
|
"智能倍增时是否对样板供应器轮询分配",
|
||||||
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
|
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
|
||||||
"注意:所有相关供应器需开启智能倍增,否则可能失效")
|
"注意:所有相关供应器需开启智能倍增,否则可能失效"
|
||||||
|
)
|
||||||
.define("providerRoundRobinEnable", true);
|
.define("providerRoundRobinEnable", true);
|
||||||
|
SMART_SCALING_MAX_MULTIPLIER = serverBuilder
|
||||||
// 智能倍增的最大倍数(以单次样板产出为单位)。
|
.comment(
|
||||||
// 0 表示不限制;>0 表示最大倍增倍数上限,例如 64 表示最多放大到 64 倍。
|
|
||||||
SMART_SCALING_MAX_MULTIPLIER = builder.comment(
|
|
||||||
"智能倍增的最大倍数(0 表示不限制)",
|
"智能倍增的最大倍数(0 表示不限制)",
|
||||||
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模")
|
"此倍数是针对单次样板产出的放大倍数上限,用于限制一次推送中按倍增缩放的规模"
|
||||||
|
)
|
||||||
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
|
.defineInRange("smartScalingMaxMultiplier", 0, 0, 1048576);
|
||||||
|
CRAFTING_PAUSE_THRESHOLD = serverBuilder
|
||||||
|
.comment(
|
||||||
|
"值越大将减少AE构建合成计划过程中的 wait/notify 次数,提升吞吐但会降低调度响应性"
|
||||||
|
)
|
||||||
|
.defineInRange("craftingPauseThreshold", 100000, 100, Integer.MAX_VALUE);
|
||||||
|
serverBuilder.pop();
|
||||||
|
|
||||||
builder.pop(); // pop smart
|
serverBuilder.push("wireless");
|
||||||
|
WIRELESS_MAX_RANGE = serverBuilder
|
||||||
// Wireless settings
|
.comment(
|
||||||
builder.push("wireless");
|
|
||||||
// 无线收发器:最大连接距离(单位:方块)。
|
|
||||||
// 一对多从端连接主端时,将以该值作为范围限制。
|
|
||||||
WIRELESS_MAX_RANGE = builder.comment(
|
|
||||||
"无线收发器最大连接距离(单位:方块)",
|
"无线收发器最大连接距离(单位:方块)",
|
||||||
"从端与主端的直线距离需小于等于该值才会建立连接。")
|
"从端与主端的直线距离需小于等于该值才会建立连接。"
|
||||||
|
)
|
||||||
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
|
.defineInRange("wirelessMaxRange", 256.0D, 1.0D, 4096.0D);
|
||||||
|
WIRELESS_CROSS_DIM_ENABLE = serverBuilder
|
||||||
// 是否允许跨维度连接(忽略维度差异进行频道传输)。
|
.comment(
|
||||||
WIRELESS_CROSS_DIM_ENABLE = builder.comment(
|
|
||||||
"是否允许无线收发器跨维度建立连接",
|
"是否允许无线收发器跨维度建立连接",
|
||||||
"开启后,从端可连接到不同维度的主端(忽略距离限制)")
|
"开启后,从端可连接到不同维度的主端(忽略距离限制)"
|
||||||
|
)
|
||||||
.define("wirelessCrossDimEnable", true);
|
.define("wirelessCrossDimEnable", true);
|
||||||
|
serverBuilder.pop();
|
||||||
|
|
||||||
builder.pop(); // pop wireless
|
serverBuilder.push("entitySpeedTicker");
|
||||||
|
ENTITY_TICKER_COST = serverBuilder
|
||||||
|
.comment(
|
||||||
// builder.pop(); // pop extendedae_plus
|
"实体加速器能量消耗基础值"
|
||||||
COMMON_SPEC = builder.build();
|
)
|
||||||
|
.defineInRange("entityTickerCost", 512, 0, Integer.MAX_VALUE);
|
||||||
|
ENTITY_TICKER_BLACK_LIST = serverBuilder
|
||||||
|
.comment(
|
||||||
|
"实体加速器黑名单:匹配的方块将不会被加速。支持通配符/正则(例如:minecraft:*)",
|
||||||
|
"格式:全名或通配符/正则字符串,例如 'minecraft:chest'、'minecraft:*'、'modid:.*_fluid'"
|
||||||
|
)
|
||||||
|
.defineListAllowEmpty(
|
||||||
|
List.of("entityTickerBlackList"),
|
||||||
|
List::of,
|
||||||
|
() -> "",
|
||||||
|
obj -> obj instanceof String
|
||||||
|
);
|
||||||
|
ENTITY_TICKER_MULTIPLIERS = serverBuilder
|
||||||
|
.comment(
|
||||||
|
"额外消耗倍率配置:为某些方块设置额外能量倍率,格式 'modid:blockid multiplier',例如 'minecraft:chest 2x'",
|
||||||
|
"支持通配符/正则匹配(例如 'minecraft:* 2x' 会对整个命名空间生效)。"
|
||||||
|
)
|
||||||
|
.defineListAllowEmpty(
|
||||||
|
List.of("entityTickerMultipliers"),
|
||||||
|
List::of,
|
||||||
|
() -> "",
|
||||||
|
obj -> obj instanceof String
|
||||||
|
);
|
||||||
|
PRIORITIZE_DISK_ENERGY = serverBuilder
|
||||||
|
.comment(
|
||||||
|
"是否优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)",
|
||||||
|
"开启后,将优先尝试从磁盘提取FE能量;反之优先消耗AE网络中的能量"
|
||||||
|
)
|
||||||
|
.define("prioritizeDiskEnergy", true);
|
||||||
|
serverBuilder.pop();
|
||||||
|
SERVER_SPEC = serverBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModConfig() {}
|
private ModConfig() {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.extendedae_plus.content.matrix;
|
||||||
|
|
||||||
|
import com.glodblock.github.extendedae.common.blocks.matrix.BlockAssemblerMatrixBase;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExtendedAE_Plus: 装配矩阵上传核心方块(内部功能块)。
|
||||||
|
* 仅用于作为多方块内部的"功能块"存在;是否允许自动上传由工具类检查集群中是否存在该核心决定。
|
||||||
|
*/
|
||||||
|
public class UploadCoreBlock extends BlockAssemblerMatrixBase<UploadCoreBlockEntity> {
|
||||||
|
|
||||||
|
public UploadCoreBlock() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UploadCoreBlock(BlockBehaviour.Properties props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Item getPresentItem() {
|
||||||
|
// 由对应的 BlockItem 注册返回,上传核心不需要特殊的 PresentItem,可返回自身的 BlockItem
|
||||||
|
return com.extendedae_plus.init.ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.extendedae_plus.content.matrix;
|
||||||
|
|
||||||
|
import com.glodblock.github.extendedae.common.me.matrix.ClusterAssemblerMatrix;
|
||||||
|
import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixFunction;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExtendedAE_Plus: 装配矩阵上传核心方块实体。
|
||||||
|
* 作为矩阵内部功能块,仅用于标记该矩阵允许被自动上传(工具类会在集群中查找此实体)。
|
||||||
|
*/
|
||||||
|
public class UploadCoreBlockEntity extends TileAssemblerMatrixFunction {
|
||||||
|
|
||||||
|
public UploadCoreBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
super((BlockEntityType<?>) com.extendedae_plus.init.ModBlockEntities.UPLOAD_CORE_BE.get(), pos, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(ClusterAssemblerMatrix c) {
|
||||||
|
// 无需修改集群,仅作为存在性标记。
|
||||||
|
// 若后续需要限制为"最多一个",可在 ExtendedAE_Plus 工具类或事件中做校验与提示。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.extendedae_plus.content.wireless;
|
||||||
|
|
||||||
import appeng.api.networking.*;
|
import appeng.api.networking.*;
|
||||||
import appeng.api.util.AECableType;
|
import appeng.api.util.AECableType;
|
||||||
|
import appeng.blockentity.AEBaseBlockEntity;
|
||||||
import com.extendedae_plus.init.ModBlockEntities;
|
import com.extendedae_plus.init.ModBlockEntities;
|
||||||
import com.extendedae_plus.init.ModItems;
|
import com.extendedae_plus.init.ModItems;
|
||||||
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||||
|
|
@ -13,7 +14,6 @@ import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ import java.util.Objects;
|
||||||
* - 集成 AE2 节点;
|
* - 集成 AE2 节点;
|
||||||
* - 集成无线主/从逻辑。
|
* - 集成无线主/从逻辑。
|
||||||
*/
|
*/
|
||||||
public class WirelessTransceiverBlockEntity extends BlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
|
public class WirelessTransceiverBlockEntity extends AEBaseBlockEntity implements IWirelessEndpoint, IInWorldGridNodeHost {
|
||||||
|
|
||||||
private IManagedGridNode managedNode;
|
private IManagedGridNode managedNode;
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public appeng.api.util.AECableType getCableConnectionType(Direction dir) {
|
public AECableType getCableConnectionType(Direction dir) {
|
||||||
// 根据相邻方块的实际连接类型渲染(优先采用相邻主机返回的类型),回退为 GLASS。
|
// 根据相邻方块的实际连接类型渲染(优先采用相邻主机返回的类型),回退为 GLASS。
|
||||||
if (this.level == null) return AECableType.GLASS;
|
if (this.level == null) return AECableType.GLASS;
|
||||||
var adjacentPos = this.worldPosition.relative(dir);
|
var adjacentPos = this.worldPosition.relative(dir);
|
||||||
|
|
@ -188,7 +188,7 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
||||||
|
|
||||||
/* ===================== NBT ===================== */
|
/* ===================== NBT ===================== */
|
||||||
@Override
|
@Override
|
||||||
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
public void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
super.saveAdditional(tag, registries);
|
super.saveAdditional(tag, registries);
|
||||||
tag.putLong("frequency", frequency);
|
tag.putLong("frequency", frequency);
|
||||||
tag.putBoolean("master", masterMode);
|
tag.putBoolean("master", masterMode);
|
||||||
|
|
@ -199,8 +199,8 @@ public class WirelessTransceiverBlockEntity extends BlockEntity implements IWire
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
public void loadTag(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
super.loadAdditional(tag, registries);
|
super.loadTag(tag, registries);
|
||||||
this.frequency = tag.getLong("frequency");
|
this.frequency = tag.getLong("frequency");
|
||||||
this.masterMode = tag.getBoolean("master");
|
this.masterMode = tag.getBoolean("master");
|
||||||
this.locked = tag.getBoolean("locked");
|
this.locked = tag.getBoolean("locked");
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package com.extendedae_plus.init;
|
||||||
import appeng.blockentity.crafting.CraftingBlockEntity;
|
import appeng.blockentity.crafting.CraftingBlockEntity;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
|
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
|
||||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
|
import com.extendedae_plus.content.matrix.UploadCoreBlockEntity;
|
||||||
import net.minecraft.core.registries.Registries;
|
import appeng.blockentity.crafting.CraftingBlockEntity;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.neoforged.neoforge.registries.DeferredHolder;
|
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
@ -42,4 +42,10 @@ public final class ModBlockEntities {
|
||||||
ref.set(type);
|
ref.set(type);
|
||||||
return type;
|
return type;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 装配矩阵上传核心方块实体
|
||||||
|
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<UploadCoreBlockEntity>> UPLOAD_CORE_BE =
|
||||||
|
BLOCK_ENTITY_TYPES.register("upload_core",
|
||||||
|
() -> BlockEntityType.Builder.of(UploadCoreBlockEntity::new,
|
||||||
|
ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get()).build(null));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ package com.extendedae_plus.init;
|
||||||
import appeng.block.crafting.CraftingUnitBlock;
|
import appeng.block.crafting.CraftingUnitBlock;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlock;
|
import com.extendedae_plus.content.matrix.UploadCoreBlock;
|
||||||
|
import appeng.block.crafting.CraftingUnitBlock;
|
||||||
|
import appeng.blockentity.crafting.CraftingBlockEntity;
|
||||||
|
import appeng.core.definitions.AEBlockEntities;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
import net.minecraft.world.level.material.MapColor;
|
import net.minecraft.world.level.material.MapColor;
|
||||||
|
|
@ -71,4 +74,15 @@ public final class ModBlocks {
|
||||||
return new CraftingUnitBlock(EPlusCraftingUnitType.ACCELERATOR_1024x);
|
return new CraftingUnitBlock(EPlusCraftingUnitType.ACCELERATOR_1024x);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 装配矩阵上传核心方块
|
||||||
|
public static final DeferredBlock<UploadCoreBlock> ASSEMBLER_MATRIX_UPLOAD_CORE = BLOCKS.register(
|
||||||
|
"assembler_matrix_upload_core",
|
||||||
|
() -> new UploadCoreBlock(
|
||||||
|
BlockBehaviour.Properties.of()
|
||||||
|
.mapColor(MapColor.METAL)
|
||||||
|
.strength(1.5F, 6.0F)
|
||||||
|
.requiresCorrectToolForDrops()
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,8 @@ import net.neoforged.neoforge.registries.DeferredHolder;
|
||||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
public final class ModCreativeTabs {
|
public final class ModCreativeTabs {
|
||||||
private ModCreativeTabs() {}
|
|
||||||
|
|
||||||
public static final DeferredRegister<CreativeModeTab> TABS =
|
public static final DeferredRegister<CreativeModeTab> TABS =
|
||||||
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExtendedAEPlus.MODID);
|
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, ExtendedAEPlus.MODID);
|
||||||
|
|
||||||
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> MAIN = TABS.register("main",
|
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> MAIN = TABS.register("main",
|
||||||
() -> CreativeModeTab.builder()
|
() -> CreativeModeTab.builder()
|
||||||
.title(Component.translatable("itemGroup." + ExtendedAEPlus.MODID + ".main"))
|
.title(Component.translatable("itemGroup." + ExtendedAEPlus.MODID + ".main"))
|
||||||
|
|
@ -26,8 +23,19 @@ public final class ModCreativeTabs {
|
||||||
output.accept(ModItems.ACCELERATOR_64x.get());
|
output.accept(ModItems.ACCELERATOR_64x.get());
|
||||||
output.accept(ModItems.ACCELERATOR_256x.get());
|
output.accept(ModItems.ACCELERATOR_256x.get());
|
||||||
output.accept(ModItems.ACCELERATOR_1024x.get());
|
output.accept(ModItems.ACCELERATOR_1024x.get());
|
||||||
|
output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get());
|
||||||
|
output.accept(ModItems.CHANNEL_CARD.get());
|
||||||
|
output.accept(ModItems.ENTITY_TICKER_PART_ITEM.get());
|
||||||
|
// 放入四个预设的 stacks(x2,x4,x8,x16),使用 ModItems 工厂创建
|
||||||
|
output.accept(ModItems.createEntitySpeedCardStack((byte) 2));
|
||||||
|
output.accept(ModItems.createEntitySpeedCardStack((byte) 4));
|
||||||
|
output.accept(ModItems.createEntitySpeedCardStack((byte) 8));
|
||||||
|
output.accept(ModItems.createEntitySpeedCardStack((byte) 16));
|
||||||
|
|
||||||
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
|
output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get());
|
||||||
})
|
})
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
|
private ModCreativeTabs() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package com.extendedae_plus.init;
|
package com.extendedae_plus.init;
|
||||||
|
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
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.ChannelCardItem;
|
||||||
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
|
||||||
import net.minecraft.world.item.BlockItem;
|
import net.minecraft.world.item.BlockItem;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.neoforged.neoforge.registries.DeferredItem;
|
import net.neoforged.neoforge.registries.DeferredItem;
|
||||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
|
|
||||||
|
|
@ -48,8 +52,39 @@ public final class ModItems {
|
||||||
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
|
() -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
public static final DeferredItem<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 DeferredItem<EntitySpeedCardItem> ENTITY_SPEED_CARD = ITEMS.register(
|
||||||
|
"entity_speed_card",
|
||||||
|
() -> new EntitySpeedCardItem(new Item.Properties())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 频道卡:用于AE机器的无线频道连接
|
||||||
|
public static final DeferredItem<ChannelCardItem> CHANNEL_CARD = ITEMS.register(
|
||||||
|
"channel_card",
|
||||||
|
() -> new ChannelCardItem(new Item.Properties())
|
||||||
|
);
|
||||||
|
|
||||||
public static final DeferredItem<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
|
public static final DeferredItem<Item> INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
|
||||||
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
|
"infinity_biginteger_cell", InfinityBigIntegerCellItem::new
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
|
||||||
|
*/
|
||||||
|
public static ItemStack createEntitySpeedCardStack(byte multiplier) {
|
||||||
|
return EntitySpeedCardItem.withMultiplier(multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 装配矩阵上传核心物品
|
||||||
|
public static final DeferredItem<Item> ASSEMBLER_MATRIX_UPLOAD_CORE = ITEMS.register(
|
||||||
|
"assembler_matrix_upload_core",
|
||||||
|
() -> new BlockItem(ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get(), new Item.Properties())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.extendedae_plus.init;
|
package com.extendedae_plus.init;
|
||||||
|
|
||||||
|
import appeng.menu.implementations.MenuTypeBuilder;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
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 com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.world.inventory.MenuType;
|
import net.minecraft.world.inventory.MenuType;
|
||||||
|
|
@ -16,5 +19,11 @@ public final class ModMenuTypes {
|
||||||
|
|
||||||
public static final DeferredHolder<MenuType<?>, MenuType<NetworkPatternControllerMenu>> NETWORK_PATTERN_CONTROLLER =
|
public static final DeferredHolder<MenuType<?>, MenuType<NetworkPatternControllerMenu>> NETWORK_PATTERN_CONTROLLER =
|
||||||
MENUS.register("network_pattern_controller",
|
MENUS.register("network_pattern_controller",
|
||||||
() -> IMenuTypeExtension.create((id, inv, buf) -> new NetworkPatternControllerMenu(id, inv, buf)));
|
() -> IMenuTypeExtension.create(NetworkPatternControllerMenu::new));
|
||||||
|
|
||||||
|
public static final DeferredHolder<MenuType<?>, MenuType<EntitySpeedTickerMenu>> ENTITY_TICKER_MENU =
|
||||||
|
MENUS.register("entity_speed_ticker",
|
||||||
|
() -> MenuTypeBuilder
|
||||||
|
.create(EntitySpeedTickerMenu::new, EntitySpeedTickerPart.class)
|
||||||
|
.build("entity_speed_ticker"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ public class ModNetwork {
|
||||||
// 在 Mod 构造中通过 modEventBus.addListener(ModNetwork::registerPayloadHandlers) 注册
|
// 在 Mod 构造中通过 modEventBus.addListener(ModNetwork::registerPayloadHandlers) 注册
|
||||||
public static void registerPayloadHandlers(final RegisterPayloadHandlersEvent event) {
|
public static void registerPayloadHandlers(final RegisterPayloadHandlersEvent event) {
|
||||||
var registrar = event.registrar(ExtendedAEPlus.MODID);
|
var registrar = event.registrar(ExtendedAEPlus.MODID);
|
||||||
|
registrar.playToServer(ToggleEntityTickerC2SPacket.TYPE, ToggleEntityTickerC2SPacket.STREAM_CODEC, ToggleEntityTickerC2SPacket::handle);
|
||||||
registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle);
|
registrar.playToServer(ToggleAdvancedBlockingC2SPacket.TYPE, ToggleAdvancedBlockingC2SPacket.STREAM_CODEC, ToggleAdvancedBlockingC2SPacket::handle);
|
||||||
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
|
registrar.playToServer(ToggleSmartDoublingC2SPacket.TYPE, ToggleSmartDoublingC2SPacket.STREAM_CODEC, ToggleSmartDoublingC2SPacket::handle);
|
||||||
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);
|
registrar.playToServer(ScalePatternsC2SPacket.TYPE, ScalePatternsC2SPacket.STREAM_CODEC, ScalePatternsC2SPacket::handle);
|
||||||
|
|
|
||||||
63
src/main/java/com/extendedae_plus/init/UpgradeCards.java
Normal file
63
src/main/java/com/extendedae_plus/init/UpgradeCards.java
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.extendedae_plus.init;
|
||||||
|
|
||||||
|
import appeng.api.upgrades.Upgrades;
|
||||||
|
import appeng.core.definitions.AEBlocks;
|
||||||
|
import appeng.core.definitions.AEItems;
|
||||||
|
import appeng.core.definitions.AEParts;
|
||||||
|
import appeng.core.localization.GuiText;
|
||||||
|
import net.neoforged.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");
|
||||||
|
|
||||||
|
// 频道卡:对齐旧版注册 — AE2 接口(方块与部件)
|
||||||
|
String interfaceGroup = GuiText.Interface.getTranslationKey();
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.INTERFACE, 1, interfaceGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.INTERFACE, 1, interfaceGroup);
|
||||||
|
|
||||||
|
// 频道卡:AE2 样板供应器(方块与部件)
|
||||||
|
String patternProviderGroup = "group.pattern_provider.name";
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||||
|
|
||||||
|
// 频道卡:AE2 I/O 总线与存储总线(部件)
|
||||||
|
String ioBusGroup = GuiText.IOBuses.getTranslationKey();
|
||||||
|
String storageGroup = "group.storage.name";
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.IMPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.STORAGE_BUS, 1, storageGroup);
|
||||||
|
|
||||||
|
// 频道卡:ExtendedAE 扩展组件支持(强依赖,直接引用)
|
||||||
|
// 扩展接口
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_INTERFACE, 1, interfaceGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_INTERFACE_PART, 1, interfaceGroup);
|
||||||
|
|
||||||
|
// 扩展样板供应器
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_PATTERN_PROVIDER, 1, patternProviderGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_PATTERN_PROVIDER_PART, 1, patternProviderGroup);
|
||||||
|
|
||||||
|
// 扩展 I/O 总线
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_IMPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.EX_EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
|
||||||
|
// 其他扩展总线(标签、精确、阈值等)
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.TAG_STORAGE_BUS, 1, storageGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.TAG_EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.MOD_STORAGE_BUS, 1, storageGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.MOD_EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.PRECISE_STORAGE_BUS, 1, storageGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.PRECISE_EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.THRESHOLD_EXPORT_BUS, 1, ioBusGroup);
|
||||||
|
|
||||||
|
// 超大接口
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.OVERSIZE_INTERFACE, 1, interfaceGroup);
|
||||||
|
Upgrades.add(ModItems.CHANNEL_CARD.get(), com.glodblock.github.extendedae.common.EAESingletons.OVERSIZE_INTERFACE_PART, 1, interfaceGroup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,17 @@
|
||||||
package com.extendedae_plus.integration.RecipeViewer.jei;
|
package com.extendedae_plus.integration.RecipeViewer.jei;
|
||||||
|
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
|
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||||
import mezz.jei.api.IModPlugin;
|
import mezz.jei.api.IModPlugin;
|
||||||
import mezz.jei.api.JeiPlugin;
|
import mezz.jei.api.JeiPlugin;
|
||||||
|
import mezz.jei.api.constants.VanillaTypes;
|
||||||
|
import mezz.jei.api.ingredients.subtypes.ISubtypeInterpreter;
|
||||||
|
import mezz.jei.api.ingredients.subtypes.UidContext;
|
||||||
|
import mezz.jei.api.registration.ISubtypeRegistration;
|
||||||
import mezz.jei.api.runtime.IJeiRuntime;
|
import mezz.jei.api.runtime.IJeiRuntime;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@JeiPlugin
|
@JeiPlugin
|
||||||
public class ModJeiPlugin implements IModPlugin {
|
public class ModJeiPlugin implements IModPlugin {
|
||||||
|
|
@ -20,4 +27,28 @@ public class ModJeiPlugin implements IModPlugin {
|
||||||
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
|
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
|
||||||
JeiRuntimeProxy.setRuntime(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(
|
||||||
|
VanillaTypes.ITEM_STACK,
|
||||||
|
com.extendedae_plus.init.ModItems.ENTITY_SPEED_CARD.get(),
|
||||||
|
new ISubtypeInterpreter<ItemStack>() {
|
||||||
|
@Override
|
||||||
|
public @NotNull Object getSubtypeData(@NotNull ItemStack ingredient, @NotNull UidContext context) {
|
||||||
|
// 返回你想让 JEI 区分子类型的数据,这里用 multiplier
|
||||||
|
return EntitySpeedCardItem.readMultiplier(ingredient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String getLegacyStringSubtypeInfo(@NotNull ItemStack ingredient, @NotNull UidContext context) {
|
||||||
|
// 返回同样的值给旧接口兼容
|
||||||
|
return String.valueOf(EntitySpeedCardItem.readMultiplier(ingredient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,12 @@ public enum WirelessTransceiverJadePluginComponents implements IBlockComponentPr
|
||||||
if (data.contains("masterMode") && !data.getBoolean("masterMode") && data.contains("masterPos")) {
|
if (data.contains("masterMode") && !data.getBoolean("masterMode") && data.contains("masterPos")) {
|
||||||
BlockPos pos = BlockPos.of(data.getLong("masterPos"));
|
BlockPos pos = BlockPos.of(data.getLong("masterPos"));
|
||||||
String dim = data.contains("masterDim") ? data.getString("masterDim") : "";
|
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()) {
|
if (!dim.isEmpty()) {
|
||||||
tooltip.add(Component.literal("维度: " + dim));
|
tooltip.add(Component.literal("维度: " + dim));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAcce
|
||||||
long freq = blockEntity.getFrequency();
|
long freq = blockEntity.getFrequency();
|
||||||
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
|
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
|
||||||
if (master != null && !master.isEndpointRemoved()) {
|
if (master != null && !master.isEndpointRemoved()) {
|
||||||
|
if (master instanceof WirelessTransceiverBlockEntity masterBlockEntity && masterBlockEntity.getCustomName() != null) {
|
||||||
|
data.putString("customName", masterBlockEntity.getCustomName().getString());
|
||||||
|
}
|
||||||
BlockPos pos = master.getBlockPos();
|
BlockPos pos = master.getBlockPos();
|
||||||
if (pos != null) {
|
if (pos != null) {
|
||||||
data.putLong("masterPos", pos.asLong());
|
data.putLong("masterPos", pos.asLong());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.extendedae_plus.mixin;
|
||||||
|
|
||||||
|
import org.objectweb.asm.tree.ClassNode;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||||
|
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ExtendedAEPlusMixinPlugin implements IMixinConfigPlugin {
|
||||||
|
private static boolean isJeiPresent() {
|
||||||
|
try {
|
||||||
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
|
Class.forName("mezz.jei.api.IModPlugin", false, cl);
|
||||||
|
return true;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoad(String mixinPackage) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRefMapperConfig() { return null; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||||
|
if (!isJeiPresent()) {
|
||||||
|
// Disable all JEI package mixins and any mixins that reference JEI-only helpers
|
||||||
|
if (mixinClassName.startsWith("com.extendedae_plus.mixin.jei")) return false;
|
||||||
|
if (mixinClassName.equals("com.extendedae_plus.mixin.ae2.menu.CraftConfirmMenuGoBackMixin")) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getMixins() { return null; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { }
|
||||||
|
}
|
||||||
|
|
@ -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,138 @@
|
||||||
|
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.PatternProviderMenuAdvancedSync;
|
||||||
|
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||||
|
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||||
|
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
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.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(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||||
|
super(menu, playerInventory, title, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void eap$initAdvancedBlocking(AdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||||
|
// 使用 @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回传
|
||||||
|
var conn = Minecraft.getInstance().getConnection();
|
||||||
|
if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@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) -> {
|
||||||
|
var conn = Minecraft.getInstance().getConnection();
|
||||||
|
if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
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.PatternProviderMenuAdvancedSync;
|
||||||
|
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||||
|
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||||
|
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
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.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(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||||
|
super(menu, playerInventory, title, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void eap$initAdvancedBlocking(SmallAdvPatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||||
|
// 使用 @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回传
|
||||||
|
var conn = Minecraft.getInstance().getConnection();
|
||||||
|
if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@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) -> {
|
||||||
|
var conn = Minecraft.getInstance().getConnection();
|
||||||
|
if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE);
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
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.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
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, HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
tag.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$readAdvancedFromNbt(CompoundTag tag, HolderLookup.Provider registries, 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() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
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.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
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, HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
tag.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$readSmartDoublingFromNbt(CompoundTag tag, HolderLookup.Provider registries, 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() {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
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.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$getAdvancedBlockingSynced() {
|
||||||
|
return this.eap$AdvancedBlocking;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
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.pedroksl.advanced_ae.common.logic.AdvPatternProviderLogic;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.ModConfigs;
|
||||||
|
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 ModConfigs.CRAFTING_PAUSE_THRESHOLD.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ public class EncodedPatternItemMixin {
|
||||||
// 客户端:在 HoverText 显示样板的编码玩家
|
// 客户端:在 HoverText 显示样板的编码玩家
|
||||||
@Inject(method = "appendHoverText", at = @At("TAIL"))
|
@Inject(method = "appendHoverText", at = @At("TAIL"))
|
||||||
public void epp$appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips, CallbackInfo ci){
|
public void epp$appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> lines, TooltipFlag advancedTooltips, CallbackInfo ci){
|
||||||
if (ModConfig.SHOW_ENCODE_PATTERN_PLAYER.get()) {
|
if (ModConfigs.SHOW_ENCODER_PATTERN_PLAYER.get()) {
|
||||||
var customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
var customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||||
var tag = customData.copyTag();
|
var tag = customData.copyTag();
|
||||||
if (tag.contains("encodePlayer")) {
|
if (tag.contains("encodePlayer")) {
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,4 @@ public class CraftingTreeNodeMixin {
|
||||||
// push the requestedAmount before addContainerItems is called
|
// push the requestedAmount before addContainerItems is called
|
||||||
RequestedAmountHolder.push(requestedAmount);
|
RequestedAmountHolder.push(requestedAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V",
|
|
||||||
at = @At(value = "RETURN"))
|
|
||||||
private void clearRequestedAmountOnReturn(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) {
|
|
||||||
// pop the pushed requested amount on return
|
|
||||||
RequestedAmountHolder.pop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ public abstract class CraftingTreeProcessMixin {
|
||||||
CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode;
|
CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode;
|
||||||
AEKey parentTarget = parentAcc.eap$getWhat();
|
AEKey parentTarget = parentAcc.eap$getWhat();
|
||||||
long requested = RequestedAmountHolder.get();
|
long requested = RequestedAmountHolder.get();
|
||||||
|
RequestedAmountHolder.pop();
|
||||||
|
|
||||||
// 根据配置决定是否在 provider 间轮询分配请求量(默认开启)
|
// 根据配置决定是否在 provider 间轮询分配请求量(默认开启)
|
||||||
long perProvider = 1L;
|
long perProvider = 1L;
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,6 @@ public abstract class AEBaseScreenMixin {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Debug: 标记一次发送(打开供应器UI)
|
|
||||||
try {
|
|
||||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorOpenProviderC2SPacket: {}", key);
|
|
||||||
} catch (Throwable ignored2) {}
|
|
||||||
PacketDistributor.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
|
PacketDistributor.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
|
||||||
cir.setReturnValue(true);
|
cir.setReturnValue(true);
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
|
||||||
if (!eap$isSupportedInterfaceScreen()) {
|
if (!eap$isSupportedInterfaceScreen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] init tail reached, preparing scale buttons."); } catch (Throwable ignored) {}
|
|
||||||
if (eap$x2Button == null) {
|
if (eap$x2Button == null) {
|
||||||
eap$x2Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(false, 2), NewIcon.MULTIPLY2);
|
eap$x2Button = new ActionEPPButton((b) -> eap$sendAdjustForAllConfigs(false, 2), NewIcon.MULTIPLY2);
|
||||||
eap$x2Button.setTooltip(null);
|
eap$x2Button.setTooltip(null);
|
||||||
|
|
@ -92,7 +91,6 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
|
||||||
if (!accessor.eap$getChildren().contains(eap$x10Button)) accessor.eap$getChildren().add(eap$x10Button);
|
if (!accessor.eap$getChildren().contains(eap$x10Button)) accessor.eap$getChildren().add(eap$x10Button);
|
||||||
|
|
||||||
eap$relayoutButtons();
|
eap$relayoutButtons();
|
||||||
try { LogUtils.getLogger().info("[EAP][InterfaceMixin] buttons added and laid out."); } catch (Throwable ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "containerTick", at = @At("TAIL"))
|
@Inject(method = "containerTick", at = @At("TAIL"))
|
||||||
|
|
@ -223,7 +221,7 @@ public abstract class InterfaceScreenMixin<T extends AEBaseMenu> {
|
||||||
int topPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
|
int topPos = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getTopPos();
|
||||||
int imageWidth = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
|
int imageWidth = ((AbstractContainerScreenAccessor<?>) (Object) this).eap$getImageWidth();
|
||||||
int bx = leftPos + imageWidth + 1;
|
int bx = leftPos + imageWidth + 1;
|
||||||
int by = topPos + 20;
|
int by = topPos + 70;
|
||||||
int spacing = 22;
|
int spacing = 22;
|
||||||
if (eap$divideBy2Button != null) { eap$divideBy2Button.setX(bx); eap$divideBy2Button.setY(by); }
|
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$x2Button != null) { eap$x2Button.setX(bx); eap$x2Button.setY(by + spacing); }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.client.gui;
|
||||||
|
|
||||||
|
import appeng.api.upgrades.Upgrades;
|
||||||
|
import appeng.client.gui.AEBaseScreen;
|
||||||
|
import appeng.client.gui.implementations.PatternProviderScreen;
|
||||||
|
import appeng.client.gui.layout.SlotGridLayout;
|
||||||
|
import appeng.client.gui.style.Blitter;
|
||||||
|
import appeng.client.gui.style.ScreenStyle;
|
||||||
|
import appeng.client.gui.style.SlotPosition;
|
||||||
|
import appeng.client.gui.style.WidgetStyle;
|
||||||
|
import appeng.client.gui.widgets.ToolboxPanel;
|
||||||
|
import appeng.client.gui.widgets.UpgradesPanel;
|
||||||
|
import appeng.core.localization.GuiText;
|
||||||
|
import appeng.menu.SlotSemantics;
|
||||||
|
import appeng.menu.implementations.PatternProviderMenu;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||||
|
import appeng.menu.AEBaseMenu;
|
||||||
|
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||||
|
import com.extendedae_plus.compat.AppliedFluxCompat;
|
||||||
|
import com.extendedae_plus.util.IStyleAccessor;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(value = PatternProviderScreen.class, priority = 1500, remap = false)
|
||||||
|
public abstract class PatternProviderScreenUpgradesMixin<C extends PatternProviderMenu> extends AEBaseScreen<C> {
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) {
|
||||||
|
|
||||||
|
if (!UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若已安装 AppliedFlux,则由 AE2/AppliedFlux 自己负责渲染升级面板,避免我们重复添加导致界面显示两个槽
|
||||||
|
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用改进的AppliedFlux兼容性检测
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
PatternProviderScreen<PatternProviderMenu> screen = (PatternProviderScreen<PatternProviderMenu>) (Object) this;
|
||||||
|
|
||||||
|
boolean shouldSkip = AppliedFluxCompat.shouldSkipOurUpgradePanel(screen);
|
||||||
|
|
||||||
|
if (shouldSkip) {
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 检查是否已经存在upgrades widget
|
||||||
|
try {
|
||||||
|
// 尝试添加升级面板
|
||||||
|
this.widgets.add("upgrades", new UpgradesPanel(menu.getSlots(SlotSemantics.UPGRADE), this::eap$getCompatibleUpgrades));
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
com.extendedae_plus.util.ExtendedAELogger.LOGGER.warn("[样板供应器][界面] 升级面板已存在,跳过添加: {}", e.getMessage());
|
||||||
|
return; // 如果升级面板已存在,不继续添加其他内容
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置TOOLBOX样式(完全按照AppliedFlux的方式)
|
||||||
|
var sp = new SlotPosition();
|
||||||
|
sp.setBottom(84);
|
||||||
|
sp.setRight(1);
|
||||||
|
sp.setGrid(SlotGridLayout.BREAK_AFTER_3COLS);
|
||||||
|
var ws = new WidgetStyle();
|
||||||
|
ws.setRight(2);
|
||||||
|
ws.setBottom(90);
|
||||||
|
ws.setWidth(59);
|
||||||
|
ws.setHeight(66);
|
||||||
|
style.getSlots().put("TOOLBOX", sp);
|
||||||
|
((IStyleAccessor) style).getImages().put("toolbox", Blitter.texture("guis/extra_panels.png", 128, 128).src(69, 62, 59, 66));
|
||||||
|
((IStyleAccessor) style).getWidgets().put("toolbox", ws);
|
||||||
|
|
||||||
|
// 添加工具箱面板
|
||||||
|
if (menu instanceof AEBaseMenu base && base instanceof com.extendedae_plus.bridge.IUpgradableMenu upg && upg.eap$getToolbox() != null && upg.eap$getToolbox().isPresent()) {
|
||||||
|
try {
|
||||||
|
this.widgets.add("toolbox", new ToolboxPanel(style, upg.eap$getToolbox().getName()));
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
com.extendedae_plus.util.ExtendedAELogger.LOGGER.warn("[样板供应器][界面] 工具箱面板已存在,跳过添加: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private List<Component> eap$getCompatibleUpgrades() {
|
||||||
|
var list = new ArrayList<Component>();
|
||||||
|
list.add(GuiText.CompatibleUpgrades.text());
|
||||||
|
if (menu instanceof AEBaseMenu base) {
|
||||||
|
var target = base.getTarget();
|
||||||
|
if (target instanceof PatternProviderLogicHost host) {
|
||||||
|
list.addAll(Upgrades.getTooltipLinesForMachine(host.getTerminalIcon().getItem()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatternProviderScreenUpgradesMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||||
|
super(menu, playerInventory, title, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.client.gui;
|
||||||
|
|
||||||
|
import appeng.client.gui.style.Blitter;
|
||||||
|
import appeng.client.gui.style.ScreenStyle;
|
||||||
|
import appeng.client.gui.style.WidgetStyle;
|
||||||
|
import com.extendedae_plus.util.IStyleAccessor;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Mixin(ScreenStyle.class)
|
||||||
|
public abstract class ScreenStyleMixin implements IStyleAccessor {
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow(remap = false)
|
||||||
|
private Map<String, Blitter> images;
|
||||||
|
@Final
|
||||||
|
@Shadow(remap = false)
|
||||||
|
private Map<String, WidgetStyle> widgets;
|
||||||
|
|
||||||
|
@SuppressWarnings("AddedMixinMembersNamePattern")
|
||||||
|
@Override
|
||||||
|
public Map<String, Blitter> getImages() {
|
||||||
|
return this.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("AddedMixinMembersNamePattern")
|
||||||
|
@Override
|
||||||
|
public Map<String, WidgetStyle> getWidgets() {
|
||||||
|
return this.widgets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,458 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.compat;
|
||||||
|
|
||||||
|
import appeng.api.networking.IManagedGridNode;
|
||||||
|
import appeng.api.networking.IGridConnection;
|
||||||
|
import appeng.api.networking.security.IActionSource;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
import appeng.api.upgrades.UpgradeInventories;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||||
|
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import com.extendedae_plus.bridge.CompatUpgradeProvider;
|
||||||
|
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||||
|
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
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 java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 样板供应器频道卡兼容实现:
|
||||||
|
* - 未安装 appflux 时,提供 1 个升级槽并读取频道卡;
|
||||||
|
* - 安装 appflux 时,优先从 appflux 提供的升级槽读取频道卡;
|
||||||
|
* - 建立到无线主站的网格连接。
|
||||||
|
*/
|
||||||
|
@Mixin(value = PatternProviderLogic.class, priority = 900, remap = false)
|
||||||
|
public abstract class PatternProviderLogicCompatMixin implements CompatUpgradeProvider, InterfaceWirelessLinkBridge {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty();
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private WirelessSlaveLink eap$compatLink;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private long eap$compatLastChannel = -1;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$compatClientConnected = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$compatHasInitialized = false;
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private PatternProviderLogicHost host;
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private IManagedGridNode mainNode;
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private IActionSource actionSource;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V",
|
||||||
|
at = @At("TAIL"))
|
||||||
|
private void eap$compatInit(IManagedGridNode mainNode, PatternProviderLogicHost host, int size, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
// 未安装AppliedFlux,我们需要提供升级槽
|
||||||
|
this.eap$compatUpgrades = UpgradeInventories.forMachine(
|
||||||
|
host.getTerminalIcon().getItem(), 1, this::eap$compatOnUpgradesChanged);
|
||||||
|
} else {
|
||||||
|
// 安装了AppliedFlux,我们不提供升级槽,但保留空的兼容槽用于备用
|
||||||
|
this.eap$compatUpgrades = UpgradeInventories.empty();
|
||||||
|
|
||||||
|
// 尝试监听AppliedFlux的升级变更
|
||||||
|
eap$tryHookAppliedFluxUpgradeChanges();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 初始化兼容升级槽失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void eap$compatOnUpgradesChanged() {
|
||||||
|
try {
|
||||||
|
this.host.saveChanges();
|
||||||
|
eap$compatLastChannel = -1;
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 兼容升级变更处理失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试监听AppliedFlux的升级变更
|
||||||
|
*/
|
||||||
|
@Unique
|
||||||
|
private void eap$tryHookAppliedFluxUpgradeChanges() {
|
||||||
|
try {
|
||||||
|
if (this instanceof IUpgradeableObject upgradeableObject) {
|
||||||
|
IUpgradeInventory afUpgrades = upgradeableObject.getUpgrades();
|
||||||
|
if (afUpgrades != null) {
|
||||||
|
// 我们不能直接修改AppliedFlux的升级槽回调,但我们可以定期检查
|
||||||
|
// 这里我们先记录一下,实际的检查会在tick中进行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||||
|
private void eap$compatWrite(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades", registries);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 保存兼容升级失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$compatRead(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
this.eap$compatUpgrades.readFromNBT(tag, "compat_upgrades", registries);
|
||||||
|
}
|
||||||
|
// 无论哪种模式都重新初始化
|
||||||
|
eap$compatLastChannel = -1;
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 读取兼容升级失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "addDrops", at = @At("TAIL"))
|
||||||
|
private void eap$compatDrops(List<ItemStack> drops, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
for (var s : this.eap$compatUpgrades) {
|
||||||
|
if (!s.isEmpty()) drops.add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 掉落兼容升级失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "clearContent", at = @At("TAIL"))
|
||||||
|
private void eap$compatClear(CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
this.eap$compatUpgrades.clear();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 清理兼容升级失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onMainNodeStateChanged", at = @At("TAIL"))
|
||||||
|
private void eap$compatOnNodeChange(CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
eap$compatLastChannel = -1;
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
// 直接初始化,不使用延迟
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||||
|
});
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 主节点状态变更处理失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$updateWirelessLink() {
|
||||||
|
if (eap$compatLink != null) {
|
||||||
|
eap$compatLink.updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
public void eap$compatInitializeChannelLink() {
|
||||||
|
try {
|
||||||
|
// 客户端早退
|
||||||
|
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eap$compatHasInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mainNode == null || mainNode.getNode() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long channel = 0L;
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
IUpgradeInventory upgrades = null;
|
||||||
|
|
||||||
|
// 优先尝试从AppliedFlux获取升级槽(如果安装了的话)
|
||||||
|
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
// 安装了appflux:优先使用appflux的升级槽
|
||||||
|
try {
|
||||||
|
// 更安全的方式获取AppliedFlux升级槽
|
||||||
|
upgrades = eap$getAppliedFluxUpgrades();
|
||||||
|
if (upgrades != null) {
|
||||||
|
} else {
|
||||||
|
ExtendedAELogger.LOGGER.warn("[样板供应器] 无法获取 appflux 升级槽,回退到兼容槽");
|
||||||
|
upgrades = this.eap$compatUpgrades;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 获取 appflux 升级槽失败,回退到兼容槽", t);
|
||||||
|
upgrades = this.eap$compatUpgrades;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未安装appflux:使用我们的兼容升级槽
|
||||||
|
upgrades = this.eap$compatUpgrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双重保险:如果主要方式失败,尝试备用方式
|
||||||
|
if (upgrades == null || !eap$hasChannelCard(upgrades)) {
|
||||||
|
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
// 如果我们的槽无频道卡,尝试检查是否有AppliedFlux的槽
|
||||||
|
try {
|
||||||
|
IUpgradeInventory backupUpgrades = eap$getAppliedFluxUpgrades();
|
||||||
|
if (backupUpgrades != null && eap$hasChannelCard(backupUpgrades)) {
|
||||||
|
upgrades = backupUpgrades;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果AppliedFlux的槽无频道卡,尝试我们的兼容槽
|
||||||
|
if (this.eap$compatUpgrades != null && eap$hasChannelCard(this.eap$compatUpgrades)) {
|
||||||
|
upgrades = this.eap$compatUpgrades;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upgrades != null) {
|
||||||
|
for (ItemStack stack : upgrades) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
channel = ChannelCardItem.getChannel(stack);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
if (eap$compatLink != null) {
|
||||||
|
eap$compatLink.setFrequency(0L);
|
||||||
|
eap$compatLink.updateStatus();
|
||||||
|
}
|
||||||
|
eap$compatLastChannel = 0L;
|
||||||
|
eap$compatHasInitialized = true;
|
||||||
|
try { host.saveChanges(); } catch (Throwable ignored) {}
|
||||||
|
// 唤醒节点,加速 AE2 感知到连接断开
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||||
|
// 兜底:如仍存在针对无线主端的直连(非 in-world),强制销毁
|
||||||
|
try {
|
||||||
|
for (IGridConnection gc : node.getConnections()) {
|
||||||
|
if (gc != null && !gc.isInWorld()) {
|
||||||
|
var other = gc.getOtherSide(node);
|
||||||
|
if (other != null && other.getOwner() instanceof com.extendedae_plus.wireless.IWirelessEndpoint) {
|
||||||
|
gc.destroy();
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored2) {}
|
||||||
|
try { if (other.getGrid() != null) { other.getGrid().getTickManager().wakeDevice(other); } } catch (Throwable ignored2) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored2) {}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eap$compatLink == null) {
|
||||||
|
var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode());
|
||||||
|
eap$compatLink = new WirelessSlaveLink(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
eap$compatLink.setFrequency(channel);
|
||||||
|
eap$compatLink.updateStatus();
|
||||||
|
eap$compatLastChannel = channel; // 记录当前频道
|
||||||
|
try { host.saveChanges(); } catch (Throwable ignored) {}
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (eap$compatLink.isConnected()) {
|
||||||
|
eap$compatHasInitialized = true;
|
||||||
|
} else {
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
// 如果连接失败,唤醒设备以便稍后重试
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 初始化频道链接失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setClientWirelessState(boolean connected) {
|
||||||
|
eap$compatClientConnected = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$isWirelessConnected() {
|
||||||
|
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return eap$compatClientConnected;
|
||||||
|
} else {
|
||||||
|
return eap$compatLink != null && eap$compatLink.isConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$hasTickInitialized() {
|
||||||
|
return eap$compatHasInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setTickInitialized(boolean initialized) {
|
||||||
|
eap$compatHasInitialized = initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$handleDelayedInit() {
|
||||||
|
// 如果还未初始化,或者需要重新检查AppliedFlux升级槽
|
||||||
|
if (!eap$compatHasInitialized) {
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
} else if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
// 安装了AppliedFlux时,定期检查升级槽变化
|
||||||
|
try {
|
||||||
|
IUpgradeInventory afUpgrades = eap$getAppliedFluxUpgrades();
|
||||||
|
if (afUpgrades != null && eap$hasChannelCard(afUpgrades)) {
|
||||||
|
// 检查频道是否发生变化
|
||||||
|
for (ItemStack stack : afUpgrades) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
long newChannel = ChannelCardItem.getChannel(stack);
|
||||||
|
if (newChannel != eap$compatLastChannel) {
|
||||||
|
eap$compatLastChannel = -1; // 强制重新初始化
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (eap$compatLastChannel != 0L) {
|
||||||
|
// 频道卡被移除
|
||||||
|
eap$compatLastChannel = -1;
|
||||||
|
eap$compatHasInitialized = false;
|
||||||
|
eap$compatInitializeChannelLink();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指示 PatternProviderLogic 的 Ticker 是否需要保持慢速 tick 以轮询频道卡或维持无线连接。
|
||||||
|
*/
|
||||||
|
public boolean eap$shouldKeepTicking() {
|
||||||
|
try {
|
||||||
|
// 仅在服务端保持tick
|
||||||
|
if (host.getBlockEntity() == null || host.getBlockEntity().getLevel() == null || host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 未初始化:需要继续tick直到初始化完成
|
||||||
|
if (!eap$compatHasInitialized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 安装了 AppliedFlux:根据连接状态与频道卡存在性决定是否维持慢速tick
|
||||||
|
if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
// 若曾经设置过频道或者当前存在未连通的链接,则保持tick
|
||||||
|
if (eap$compatLastChannel != 0L) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (eap$compatLink != null && !eap$compatLink.isConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
IUpgradeInventory afUpgrades = eap$getAppliedFluxUpgrades();
|
||||||
|
if (afUpgrades != null && eap$hasChannelCard(afUpgrades)) {
|
||||||
|
// 槽中有频道卡,保持tick以尽快完成连接
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
// 否则可以休眠
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 未安装 AppliedFlux:当存在频道卡但连接尚未建立时保持tick
|
||||||
|
if (this.eap$compatUpgrades != null && eap$hasChannelCard(this.eap$compatUpgrades)) {
|
||||||
|
if (eap$compatLink == null || !eap$compatLink.isConnected()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompatUpgradeProvider 实现:仅在未安装 appflux 时由我们提供升级槽
|
||||||
|
@Unique
|
||||||
|
private boolean eap$hasChannelCard(IUpgradeInventory inventory) {
|
||||||
|
if (inventory == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (ItemStack stack : inventory) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地获取AppliedFlux提供的升级槽
|
||||||
|
*/
|
||||||
|
@Unique
|
||||||
|
private IUpgradeInventory eap$getAppliedFluxUpgrades() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// 检查当前对象是否实现了IUpgradeableObject接口
|
||||||
|
if (this instanceof IUpgradeableObject upgradeableObject) {
|
||||||
|
IUpgradeInventory upgrades = upgradeableObject.getUpgrades();
|
||||||
|
|
||||||
|
// 确保这不是我们自己的兼容升级槽
|
||||||
|
if (upgrades != null && upgrades != this.eap$compatUpgrades) {
|
||||||
|
return upgrades;
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器] 获取AppliedFlux升级槽时出错", t);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IUpgradeInventory eap$getCompatUpgrades() {
|
||||||
|
return this.eap$compatUpgrades != null ? this.eap$compatUpgrades : UpgradeInventories.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.helpers;
|
||||||
|
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.helpers.InterfaceLogic;
|
||||||
|
import appeng.helpers.InterfaceLogicHost;
|
||||||
|
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||||
|
import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
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(InterfaceLogic.class)
|
||||||
|
public abstract class InterfaceLogicChannelCardMixin implements InterfaceWirelessLinkBridge {
|
||||||
|
|
||||||
|
@Shadow(remap = false) public abstract IUpgradeInventory getUpgrades();
|
||||||
|
|
||||||
|
@Shadow(remap = false) public abstract appeng.api.networking.IGridNode getActionableNode();
|
||||||
|
|
||||||
|
@Shadow(remap = false) protected InterfaceLogicHost host;
|
||||||
|
|
||||||
|
@Shadow(remap = false) protected appeng.api.networking.IManagedGridNode mainNode;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private WirelessSlaveLink eap$link;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private long eap$lastChannel = -1;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$clientConnected = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$hasInitialized = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private int eap$delayedInitTicks = 0;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// InterfaceLogicChannelCardMixin 已加载
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$onUpgradesChangedTail(CallbackInfo ci) {
|
||||||
|
// 升级变更时重置标志并尝试初始化
|
||||||
|
eap$lastChannel = -1;
|
||||||
|
eap$hasInitialized = false;
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "gridChanged", at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$afterGridChanged(CallbackInfo ci) {
|
||||||
|
// 网格状态变化时重置标志并设置延迟初始化
|
||||||
|
eap$lastChannel = -1;
|
||||||
|
eap$hasInitialized = false;
|
||||||
|
eap$delayedInitTicks = 10; // 适当增加延迟tick,等待网格完成引导
|
||||||
|
// 尝试唤醒设备,确保后续还能继续tick
|
||||||
|
if (mainNode != null) {
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try {
|
||||||
|
grid.getTickManager().wakeDevice(node);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$afterReadNBT(net.minecraft.nbt.CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
// 从 NBT加载时重置标志
|
||||||
|
eap$lastChannel = -1;
|
||||||
|
eap$hasInitialized = false;
|
||||||
|
// 直接尝试初始化
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "clearContent", at = @At("HEAD"), remap = false)
|
||||||
|
private void eap$onClearContent(CallbackInfo ci) {
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.onUnloadOrRemove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
public void eap$initializeChannelLink() {
|
||||||
|
// 仅在服务端执行,避免在渲染线程/客户端触发任何初始化路径
|
||||||
|
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免重复初始化
|
||||||
|
if (eap$hasInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅要求节点对象可用;不要依赖 isActive(无线连接本身会建立连接与激活节点)
|
||||||
|
if (mainNode == null || mainNode.getNode() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
long channel = 0L;
|
||||||
|
boolean found = false;
|
||||||
|
for (ItemStack stack : getUpgrades()) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
channel = ChannelCardItem.getChannel(stack);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
// 无频道卡:断开并视为初始化完成
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.setFrequency(0L);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
}
|
||||||
|
eap$hasInitialized = true;
|
||||||
|
// 保存一次状态
|
||||||
|
try { host.saveChanges(); } catch (Throwable ignored) {}
|
||||||
|
// 唤醒设备以刷新客户端/邻居
|
||||||
|
try {
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored2) {}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored2) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eap$link == null) {
|
||||||
|
var endpoint = new InterfaceNodeEndpointImpl(host, () -> this.mainNode.getNode());
|
||||||
|
eap$link = new WirelessSlaveLink(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
eap$link.setFrequency(channel);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
try { host.saveChanges(); } catch (Throwable ignored) {}
|
||||||
|
// 唤醒设备,加速后续 tick 以完成连接
|
||||||
|
try {
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored2) {}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored2) {}
|
||||||
|
|
||||||
|
if (eap$link.isConnected()) {
|
||||||
|
eap$hasInitialized = true; // 设置初始化完成标志
|
||||||
|
} else {
|
||||||
|
// 不标记为完成,允许后续tick重试
|
||||||
|
eap$hasInitialized = false;
|
||||||
|
// 设置一个短延迟窗口,避免每tick刷屏
|
||||||
|
eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5);
|
||||||
|
try {
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try {
|
||||||
|
grid.getTickManager().wakeDevice(node);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$updateWirelessLink() {
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$isWirelessConnected() {
|
||||||
|
// InterfaceLogic没有isClientSide方法,需要通过host判断
|
||||||
|
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return eap$clientConnected;
|
||||||
|
} else {
|
||||||
|
return eap$link != null && eap$link.isConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setClientWirelessState(boolean connected) {
|
||||||
|
eap$clientConnected = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$hasTickInitialized() {
|
||||||
|
return eap$hasInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setTickInitialized(boolean initialized) {
|
||||||
|
eap$hasInitialized = initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$handleDelayedInit() {
|
||||||
|
// 仅在服务端执行延迟初始化,避免在渲染线程/客户端触发任何初始化路径
|
||||||
|
if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若尚未初始化,则持续尝试,直到网格完成引导
|
||||||
|
if (!eap$hasInitialized) {
|
||||||
|
// 若节点对象尚未就绪,则等待;无需等待 isActive(无线接入后会激活)
|
||||||
|
if (mainNode == null || mainNode.getNode() == null) {
|
||||||
|
// 仍在引导,消耗计时器
|
||||||
|
if (eap$delayedInitTicks > 0) {
|
||||||
|
eap$delayedInitTicks--;
|
||||||
|
}
|
||||||
|
if (eap$delayedInitTicks == 0) {
|
||||||
|
// 重新设定一个短延迟窗口,并唤醒设备,以保证后续还能继续 tick
|
||||||
|
eap$delayedInitTicks = 5;
|
||||||
|
try {
|
||||||
|
mainNode.ifPresent((grid, node) -> {
|
||||||
|
try {
|
||||||
|
grid.getTickManager().wakeDevice(node);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 网格已引导完成,执行初始化
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eap$initializeChannelLink方法已在上面实现
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.helpers;
|
||||||
|
|
||||||
|
import appeng.helpers.InterfaceLogic;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入到 InterfaceLogic.Ticker 的每tick回调,驱动无线链接状态更新与延迟初始化。
|
||||||
|
*/
|
||||||
|
@Mixin(targets = "appeng.helpers.InterfaceLogic$Ticker")
|
||||||
|
public abstract class InterfaceLogicTickerMixin {
|
||||||
|
|
||||||
|
// Mixin 访问内部类的外部引用字段(javac 生成名 this$0)
|
||||||
|
@Shadow(remap = false)
|
||||||
|
@Final
|
||||||
|
private InterfaceLogic this$0;
|
||||||
|
|
||||||
|
@Inject(method = "tickingRequest", at = @At("HEAD"), remap = false)
|
||||||
|
private void eap$tickHead(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
|
||||||
|
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||||
|
// 仅在服务端处理延迟初始化,避免客户端干扰
|
||||||
|
if (node != null && node.getLevel() != null && node.getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
|
||||||
|
bridge.eap$handleDelayedInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "tickingRequest", at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
|
||||||
|
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||||
|
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
|
||||||
|
bridge.eap$updateWirelessLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.helpers;
|
||||||
|
|
||||||
|
import appeng.api.networking.IManagedGridNode;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.UpgradeInventories;
|
||||||
|
import appeng.helpers.InterfaceLogic;
|
||||||
|
import appeng.helpers.InterfaceLogicHost;
|
||||||
|
import com.extendedae_plus.bridge.CompatUpgradeProvider;
|
||||||
|
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||||
|
import net.neoforged.fml.ModList;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为ME接口增加升级槽数量
|
||||||
|
* - 没有AppliedFlux时:从1个增加到2个
|
||||||
|
* - 有AppliedFlux时:从2个增加到3个
|
||||||
|
*/
|
||||||
|
@Mixin(value = InterfaceLogic.class, priority = 1100, remap = false)
|
||||||
|
public abstract class InterfaceLogicUpgradesMixin implements CompatUpgradeProvider {
|
||||||
|
|
||||||
|
@Shadow(remap = false)
|
||||||
|
@Final
|
||||||
|
@Mutable
|
||||||
|
private IUpgradeInventory upgrades;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private IUpgradeInventory eap$compatUpgrades;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在AppliedFlux之后进一步增加升级槽数量
|
||||||
|
* mixin优先级设置为1100,确保在AppliedFlux的mixin之后执行
|
||||||
|
*/
|
||||||
|
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/InterfaceLogicHost;Lnet/minecraft/world/item/Item;I)V",
|
||||||
|
at = @At("TAIL"), remap = false)
|
||||||
|
private void eap$expandUpgradesAfterAppliedFlux(IManagedGridNode gridNode, InterfaceLogicHost host, Item is, int slots, CallbackInfo ci) {
|
||||||
|
boolean hasAppliedFlux = ModList.get().isLoaded("appflux");
|
||||||
|
int currentSlots = this.upgrades.size();
|
||||||
|
int targetSlots;
|
||||||
|
|
||||||
|
if (hasAppliedFlux) {
|
||||||
|
// AppliedFlux已经将升级槽从1增加到2,我们再增加1个变成3
|
||||||
|
targetSlots = 3;
|
||||||
|
} else {
|
||||||
|
// 没有AppliedFlux,从原始的1增加到2
|
||||||
|
targetSlots = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有当当前槽数小于目标槽数时才需要扩展
|
||||||
|
if (currentSlots < targetSlots) {
|
||||||
|
this.upgrades = UpgradeInventories.forMachine(is, targetSlots, this::eap$onUpgradesChanged);
|
||||||
|
|
||||||
|
// 设置兼容升级槽
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
this.eap$compatUpgrades = this.upgrades;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void eap$onUpgradesChanged() {
|
||||||
|
// 调用原始的onUpgradesChanged方法
|
||||||
|
try {
|
||||||
|
// 通过反射调用原始方法,因为它是protected的
|
||||||
|
var method = InterfaceLogic.class.getDeclaredMethod("onUpgradesChanged");
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[ME接口] 调用onUpgradesChanged失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IUpgradeInventory eap$getCompatUpgrades() {
|
||||||
|
return this.eap$compatUpgrades != null ? this.eap$compatUpgrades : this.upgrades;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ import java.util.Collections;
|
||||||
@Mixin(value = PatternProviderLogic.class, remap = false)
|
@Mixin(value = PatternProviderLogic.class, remap = false)
|
||||||
public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
|
public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder {
|
||||||
@Unique
|
@Unique
|
||||||
private static final String EPP_ADV_BLOCKING_KEY = "epp_advanced_blocking";
|
private static final String EAP_ADV_BLOCKING_KEY = "epp_advanced_blocking";
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private boolean eap$advancedBlocking = false;
|
private boolean eap$advancedBlocking = false;
|
||||||
|
|
@ -38,13 +38,13 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder
|
||||||
|
|
||||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||||
private void eap$writeAdvancedToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
private void eap$writeAdvancedToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, 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"))
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
private void eap$readAdvancedFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
private void eap$readAdvancedFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
if (tag.contains(EPP_ADV_BLOCKING_KEY)) {
|
if (tag.contains(EAP_ADV_BLOCKING_KEY)) {
|
||||||
this.eap$advancedBlocking = tag.getBoolean(EPP_ADV_BLOCKING_KEY);
|
this.eap$advancedBlocking = tag.getBoolean(EAP_ADV_BLOCKING_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
@Mixin(value = PatternProviderLogic.class, remap = false)
|
@Mixin(value = PatternProviderLogic.class, remap = false)
|
||||||
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
||||||
@Unique
|
@Unique
|
||||||
private static final String EPP_SMART_DOUBLING_KEY = "epp_smart_doubling";
|
private static final String EAP_SMART_DOUBLING_KEY = "epp_smart_doubling";
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private boolean eap$smartDoubling = false;
|
private boolean eap$smartDoubling = false;
|
||||||
|
|
@ -46,13 +46,13 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
||||||
|
|
||||||
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||||
private void eap$writeSmartDoublingToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
private void eap$writeSmartDoublingToNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, 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"))
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
private void eap$readSmartDoublingFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
private void eap$readSmartDoublingFromNbt(CompoundTag tag, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
if (tag.contains(EPP_SMART_DOUBLING_KEY)) {
|
if (tag.contains(EAP_SMART_DOUBLING_KEY)) {
|
||||||
this.eap$smartDoubling = tag.getBoolean(EPP_SMART_DOUBLING_KEY);
|
this.eap$smartDoubling = tag.getBoolean(EAP_SMART_DOUBLING_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.helpers;
|
||||||
|
|
||||||
|
import appeng.api.networking.IManagedGridNode;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
import appeng.api.upgrades.UpgradeInventories;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.fml.ModList;
|
||||||
|
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 java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为样板供应器添加升级槽支持
|
||||||
|
* - 没有 AppliedFlux 时:添加 1 个升级槽
|
||||||
|
* - 有 AppliedFlux 时:在其基础上再增加 1 个(总共 2 个)
|
||||||
|
*
|
||||||
|
* 优先级 1100 确保在 AppliedFlux (默认优先级 1000) 之后执行
|
||||||
|
*/
|
||||||
|
@Mixin(value = PatternProviderLogic.class, priority = 1100, remap = false)
|
||||||
|
public abstract class PatternProviderLogicUpgradesMixin {
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private PatternProviderLogicHost host;
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private IManagedGridNode mainNode;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private IUpgradeInventory eap$upgrades = UpgradeInventories.empty();
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$hasAppliedFlux = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$upgradesInitialized = false;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V",
|
||||||
|
at = @At("TAIL"))
|
||||||
|
private void eap$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
// 检测是否安装了 AppliedFlux
|
||||||
|
this.eap$hasAppliedFlux = ModList.get().isLoaded("appflux");
|
||||||
|
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
// AppliedFlux 已安装,尝试获取并扩展其升级槽
|
||||||
|
eap$extendAppliedFluxUpgrades();
|
||||||
|
} // 未安装 AppliedFlux 的情况由 CompatMixin 负责创建兼容升级槽
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 初始化失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void eap$extendAppliedFluxUpgrades() {
|
||||||
|
try {
|
||||||
|
// 通过反射直接读取 AppliedFlux Mixin 注入的字段:af_upgrades
|
||||||
|
IUpgradeInventory existingUpgrades = null;
|
||||||
|
try {
|
||||||
|
Field f = this.getClass().getDeclaredField("af_upgrades");
|
||||||
|
f.setAccessible(true);
|
||||||
|
existingUpgrades = (IUpgradeInventory) f.get(this);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingUpgrades != null && existingUpgrades != UpgradeInventories.empty()) {
|
||||||
|
// AppliedFlux 已经创建了升级槽
|
||||||
|
int currentSlots = existingUpgrades.size();
|
||||||
|
int targetSlots = 2; // AppliedFlux 1个 + 我们 1个 = 2个
|
||||||
|
|
||||||
|
|
||||||
|
if (currentSlots < targetSlots) {
|
||||||
|
// 需要扩展升级槽
|
||||||
|
// 先保存现有物品
|
||||||
|
ItemStack[] savedItems = new ItemStack[currentSlots];
|
||||||
|
for (int i = 0; i < currentSlots; i++) {
|
||||||
|
savedItems[i] = existingUpgrades.getStackInSlot(i).copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的升级槽(更多槽位)
|
||||||
|
this.eap$upgrades = UpgradeInventories.forMachine(
|
||||||
|
host.getTerminalIcon().getItem(),
|
||||||
|
targetSlots,
|
||||||
|
this::eap$onUpgradesChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
// 恢复原有物品
|
||||||
|
for (int i = 0; i < savedItems.length; i++) {
|
||||||
|
this.eap$upgrades.setItemDirect(i, savedItems[i]);
|
||||||
|
}
|
||||||
|
// 将 AF 的字段指向我们新的升级槽,保持其服务与 NBT 钩子一致
|
||||||
|
try {
|
||||||
|
Field f = this.getClass().getDeclaredField("af_upgrades");
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(this, this.eap$upgrades);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eap$upgradesInitialized = true;
|
||||||
|
} else {
|
||||||
|
// AppliedFlux 或其他模组已经提供了足够的槽位
|
||||||
|
this.eap$upgrades = existingUpgrades;
|
||||||
|
this.eap$upgradesInitialized = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AppliedFlux 还没初始化升级槽,或者出了问题,我们创建默认的
|
||||||
|
this.eap$upgrades = UpgradeInventories.forMachine(
|
||||||
|
host.getTerminalIcon().getItem(),
|
||||||
|
2,
|
||||||
|
this::eap$onUpgradesChanged
|
||||||
|
);
|
||||||
|
// 同步 AF 字段
|
||||||
|
try {
|
||||||
|
Field f = this.getClass().getDeclaredField("af_upgrades");
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(this, this.eap$upgrades);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
this.eap$upgradesInitialized = true;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 扩展 AppliedFlux 升级槽失败", t);
|
||||||
|
// 失败时创建默认数量
|
||||||
|
this.eap$upgrades = UpgradeInventories.forMachine(
|
||||||
|
host.getTerminalIcon().getItem(),
|
||||||
|
2,
|
||||||
|
this::eap$onUpgradesChanged
|
||||||
|
);
|
||||||
|
// 同步 AF 字段(最佳努力)
|
||||||
|
try {
|
||||||
|
Field f = this.getClass().getDeclaredField("af_upgrades");
|
||||||
|
f.setAccessible(true);
|
||||||
|
f.set(this, this.eap$upgrades);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
this.eap$upgradesInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void eap$onUpgradesChanged() {
|
||||||
|
try {
|
||||||
|
this.host.saveChanges();
|
||||||
|
|
||||||
|
// 如果 AppliedFlux 安装了,也调用其原始的 onUpgradesChanged 方法
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
try {
|
||||||
|
Method afMethod = this.getClass().getDeclaredMethod("af_onUpgradesChanged");
|
||||||
|
afMethod.setAccessible(true);
|
||||||
|
afMethod.invoke(this);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// AppliedFlux 的方法不存在,这是正常的
|
||||||
|
} catch (Throwable t) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] onUpgradesChanged 处理失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Inject(method = "writeToNBT", at = @At("TAIL"))
|
||||||
|
private void eap$saveUpgrades(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
// 已安装 AF 时交由 AF 的 mixin 处理,避免重复写入
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eap$upgradesInitialized && this.eap$upgrades != null && this.eap$upgrades != UpgradeInventories.empty()) {
|
||||||
|
// 根据是否有 AppliedFlux 使用不同的 NBT 键
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
// AppliedFlux 使用 "upgrades" 键,我们使用 "eap_upgrades" 避免冲突
|
||||||
|
this.eap$upgrades.writeToNBT(tag, "eap_upgrades", registries);
|
||||||
|
} else {
|
||||||
|
// 没有 AppliedFlux,使用标准键
|
||||||
|
this.eap$upgrades.writeToNBT(tag, "upgrades", registries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 保存升级槽失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$loadUpgrades(CompoundTag tag, HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
// 重新检测 AppliedFlux 状态(可能在世界加载时状态有变化)
|
||||||
|
this.eap$hasAppliedFlux = ModList.get().isLoaded("appflux");
|
||||||
|
|
||||||
|
if (!eap$upgradesInitialized) {
|
||||||
|
// 如果还没初始化,先初始化
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
eap$extendAppliedFluxUpgrades();
|
||||||
|
} else {
|
||||||
|
this.eap$upgrades = UpgradeInventories.forMachine(
|
||||||
|
host.getTerminalIcon().getItem(),
|
||||||
|
1,
|
||||||
|
this::eap$onUpgradesChanged
|
||||||
|
);
|
||||||
|
this.eap$upgradesInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已安装 AF 时,由 AF 自行从 "upgrades" 读取;我们只处理无 AF 情况
|
||||||
|
if (!eap$hasAppliedFlux && this.eap$upgrades != null && this.eap$upgrades != UpgradeInventories.empty()) {
|
||||||
|
if (tag.contains("upgrades")) {
|
||||||
|
this.eap$upgrades.readFromNBT(tag, "upgrades", registries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 加载升级槽失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "addDrops", at = @At("TAIL"))
|
||||||
|
private void eap$dropUpgrades(List<ItemStack> drops, CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
// AF 已安装时交由其自身处理掉落
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eap$upgradesInitialized && this.eap$upgrades != null) {
|
||||||
|
for (var is : this.eap$upgrades) {
|
||||||
|
if (!is.isEmpty()) {
|
||||||
|
drops.add(is);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 添加掉落失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "clearContent", at = @At("TAIL"))
|
||||||
|
private void eap$clearUpgrades(CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
// AF 已安装时交由其自身处理清理
|
||||||
|
if (eap$hasAppliedFlux) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eap$upgradesInitialized && this.eap$upgrades != null) {
|
||||||
|
this.eap$upgrades.clear();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[样板供应器][升级槽] 清空升级槽失败", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.helpers.patternprovider;
|
||||||
|
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入到 PatternProviderLogic.Ticker 的每tick回调,驱动无线链接状态更新与延迟初始化。
|
||||||
|
*/
|
||||||
|
@Mixin(targets = "appeng.helpers.patternprovider.PatternProviderLogic$Ticker", remap = false)
|
||||||
|
public abstract class PatternProviderLogicTickerMixin {
|
||||||
|
|
||||||
|
// 访问内部类的外部引用字段(javac 生成名 this$0)
|
||||||
|
@Shadow(remap = false)
|
||||||
|
@Final
|
||||||
|
private PatternProviderLogic this$0;
|
||||||
|
|
||||||
|
@Inject(method = "tickingRequest", at = @At("HEAD"))
|
||||||
|
private void eap$tickHead(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
|
||||||
|
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||||
|
// 仅在服务端处理延迟初始化
|
||||||
|
if (node != null && node.getLevel() != null && node.getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
|
||||||
|
bridge.eap$handleDelayedInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "tickingRequest", at = @At("TAIL"), cancellable = true)
|
||||||
|
private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall,
|
||||||
|
CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||||
|
// 仅在服务端设置慢速 tick
|
||||||
|
if (node != null && node.getLevel() != null && node.getLevel().isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this$0 instanceof InterfaceWirelessLinkBridge bridge) {
|
||||||
|
bridge.eap$updateWirelessLink();
|
||||||
|
if (bridge.eap$shouldKeepTicking()) {
|
||||||
|
cir.setReturnValue(appeng.api.networking.ticking.TickRateModulation.SLOWER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.menu;
|
||||||
|
|
||||||
|
import appeng.menu.AEBaseMenu;
|
||||||
|
import appeng.menu.SlotSemantics;
|
||||||
|
import appeng.menu.implementations.PatternProviderMenu;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 去重样板供应器升级槽的菜单注入:
|
||||||
|
* - 多个模组(AppliedFlux、expandedae等)都会在 PatternProviderMenu 的构造中调用 setupUpgrades,
|
||||||
|
* 导致同一套升级槽 UI 被重复添加(渲染为四个)。
|
||||||
|
* - 拦截 AEBaseMenu.setupUpgrades,在目标菜单为 PatternProviderMenu 且已存在升级槽时取消后续注入。
|
||||||
|
*/
|
||||||
|
@Mixin(value = AEBaseMenu.class, priority = 2000, remap = false)
|
||||||
|
public abstract class AEBaseMenuUpgradesDedupMixin {
|
||||||
|
|
||||||
|
@Inject(method = "setupUpgrades(Lappeng/api/upgrades/IUpgradeInventory;)V", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void eap$dedupPatternProviderUpgradeSlots(IUpgradeInventory upgrades, CallbackInfo ci) {
|
||||||
|
// 仅对样板供应器菜单去重,避免影响其他菜单
|
||||||
|
if (((Object) this) instanceof PatternProviderMenu) {
|
||||||
|
var self = (AEBaseMenu) (Object) this;
|
||||||
|
var existing = self.getSlots(SlotSemantics.UPGRADE);
|
||||||
|
if (existing != null && !existing.isEmpty()) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import appeng.menu.me.crafting.CraftingPlanSummary;
|
||||||
import appeng.menu.me.crafting.CraftingPlanSummaryEntry;
|
import appeng.menu.me.crafting.CraftingPlanSummaryEntry;
|
||||||
import com.extendedae_plus.integration.RecipeViewer.RecipeViewerHelper;
|
import com.extendedae_plus.integration.RecipeViewer.RecipeViewerHelper;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.neoforged.fml.ModList;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.menu;
|
||||||
|
|
||||||
|
import appeng.helpers.InterfaceLogicHost;
|
||||||
|
import appeng.menu.AEBaseMenu;
|
||||||
|
import appeng.menu.ToolboxMenu;
|
||||||
|
import appeng.menu.implementations.InterfaceMenu;
|
||||||
|
import com.extendedae_plus.bridge.IUpgradableMenu;
|
||||||
|
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||||
|
import com.extendedae_plus.bridge.CompatUpgradeProvider;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.inventory.MenuType;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为ME接口菜单添加升级槽支持,确保与AppliedFlux的兼容性
|
||||||
|
*/
|
||||||
|
@Mixin(value = InterfaceMenu.class, priority = 900, remap = false)
|
||||||
|
public abstract class InterfaceMenuUpgradesMixin extends AEBaseMenu implements IUpgradableMenu {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private ToolboxMenu eap$toolbox;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/InterfaceLogicHost;)V",
|
||||||
|
at = @At("TAIL"))
|
||||||
|
private void eap$initUpgrades(MenuType<?> menuType, int id, Inventory playerInventory, InterfaceLogicHost host, CallbackInfo ci) {
|
||||||
|
this.eap$toolbox = new ToolboxMenu(this);
|
||||||
|
|
||||||
|
// InterfaceMenu已经继承自UpgradeableMenu,会自动处理升级槽
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ToolboxMenu eap$getToolbox() {
|
||||||
|
return this.eap$toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InterfaceMenuUpgradesMixin(MenuType<?> menuType, int id, Inventory playerInventory, Object host) {
|
||||||
|
super(menuType, id, playerInventory, host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.menu;
|
||||||
|
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||||
|
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||||
|
import appeng.menu.AEBaseMenu;
|
||||||
|
import appeng.menu.ToolboxMenu;
|
||||||
|
import appeng.menu.implementations.PatternProviderMenu;
|
||||||
|
import com.extendedae_plus.bridge.IUpgradableMenu;
|
||||||
|
import com.extendedae_plus.compat.UpgradeSlotCompat;
|
||||||
|
import com.extendedae_plus.bridge.CompatUpgradeProvider;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
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 com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
|
||||||
|
@Mixin(value = PatternProviderMenu.class, priority = 2000, remap = false)
|
||||||
|
public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implements IUpgradableMenu {
|
||||||
|
@Final
|
||||||
|
@Shadow protected PatternProviderLogic logic;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private ToolboxMenu eap$toolbox;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V",
|
||||||
|
at = @At("TAIL"))
|
||||||
|
private void eap$initUpgrades(MenuType<?> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||||
|
this.eap$toolbox = new ToolboxMenu(this);
|
||||||
|
|
||||||
|
// 当未安装 AppliedFlux 时,我们负责注入升级槽;安装了 AF 则由 AF 的菜单 Mixin 负责,避免重复渲染
|
||||||
|
if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) {
|
||||||
|
this.setupUpgrades(((CompatUpgradeProvider) this.logic).eap$getCompatUpgrades());
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ToolboxMenu eap$getToolbox() {
|
||||||
|
return this.eap$toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatternProviderMenuUpgradesMixin(MenuType<?> menuType, int id, Inventory playerInventory, Object host) {
|
||||||
|
super(menuType, id, playerInventory, host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.parts.automation;
|
||||||
|
|
||||||
|
import appeng.api.networking.security.IActionHost;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
import appeng.helpers.InterfaceLogicHost;
|
||||||
|
import appeng.parts.automation.IOBusPart;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||||
|
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
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 的 I/O 总线注入频道卡联动:在升级变更时读取频道并更新无线链接。
|
||||||
|
*/
|
||||||
|
@Mixin(value = IOBusPart.class, remap = false)
|
||||||
|
public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private WirelessSlaveLink eap$link;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private long eap$lastChannel = -1;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$clientConnected = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$hasTickInitialized = false;
|
||||||
|
|
||||||
|
@Inject(method = "upgradesChanged", at = @At("TAIL"))
|
||||||
|
private void eap$onUpgradesChanged(CallbackInfo ci) {
|
||||||
|
// 只在服务端初始化频道链接
|
||||||
|
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "tickingRequest", at = @At("HEAD"))
|
||||||
|
private void eap$beforeTick(appeng.api.networking.IGridNode node, int ticksSinceLastCall, org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable<appeng.api.networking.ticking.TickRateModulation> cir) {
|
||||||
|
// 在第一次tick时初始化频道链接(此时网格节点已经在线)
|
||||||
|
if (!eap$hasTickInitialized && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
eap$hasTickInitialized = true;
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$afterReadFromNBT(CompoundTag extra, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
// 从NBT加载时重置频道缓存和tick初始化标志
|
||||||
|
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
eap$lastChannel = -1;
|
||||||
|
eap$hasTickInitialized = false; // 重置标志,允许再次初始化
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
public void eap$initializeChannelLink() {
|
||||||
|
// 防止重复调用
|
||||||
|
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
IUpgradeInventory inv = this.getUpgrades();
|
||||||
|
long channel = 0L;
|
||||||
|
boolean found = false;
|
||||||
|
for (var stack : inv) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
channel = ChannelCardItem.getChannel(stack);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 频道没有变化则跳过
|
||||||
|
if (eap$lastChannel == channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eap$lastChannel = channel;
|
||||||
|
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
// 无频道卡则断开
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.setFrequency(0L);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
// 立即通知客户端状态变化(断开连接无需延迟)
|
||||||
|
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eap$link == null) {
|
||||||
|
var endpoint = new GenericNodeEndpointImpl(
|
||||||
|
() -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(),
|
||||||
|
() -> ((IActionHost)(Object)this).getActionableNode()
|
||||||
|
);
|
||||||
|
eap$link = new WirelessSlaveLink(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
eap$link.setFrequency(channel);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
|
||||||
|
// 通知客户端状态变化
|
||||||
|
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[服务端] IOBus 初始化频道链接失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$updateWirelessLink() {
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$isWirelessConnected() {
|
||||||
|
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
return eap$clientConnected;
|
||||||
|
} else {
|
||||||
|
return eap$link != null && eap$link.isConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setClientWirelessState(boolean connected) {
|
||||||
|
eap$clientConnected = connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.extendedae_plus.mixin.ae2.parts.storagebus;
|
||||||
|
|
||||||
|
import appeng.api.networking.IGridNodeListener;
|
||||||
|
import appeng.api.networking.security.IActionHost;
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import appeng.api.upgrades.IUpgradeableObject;
|
||||||
|
import appeng.parts.storagebus.StorageBusPart;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
|
import com.extendedae_plus.ae.items.ChannelCardItem;
|
||||||
|
import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge;
|
||||||
|
import com.extendedae_plus.init.ModItems;
|
||||||
|
import com.extendedae_plus.wireless.WirelessSlaveLink;
|
||||||
|
import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl;
|
||||||
|
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 的存储总线注入频道卡联动:在升级变更时读取频道并更新无线链接。
|
||||||
|
*/
|
||||||
|
@Mixin(value = StorageBusPart.class, remap = false)
|
||||||
|
public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private WirelessSlaveLink eap$link;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private long eap$lastChannel = -1;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean eap$clientConnected = false;
|
||||||
|
|
||||||
|
@Inject(method = "upgradesChanged", at = @At("TAIL"))
|
||||||
|
private void eap$onUpgradesChanged(CallbackInfo ci) {
|
||||||
|
// 只在服务端初始化频道链接
|
||||||
|
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "onMainNodeStateChanged", at = @At("TAIL"))
|
||||||
|
private void eap$onMainNodeStateChanged(IGridNodeListener.State reason, CallbackInfo ci) {
|
||||||
|
// 在节点状态变化时(包括加载后的GRID_BOOT)重新初始化频道链接
|
||||||
|
if (reason == IGridNodeListener.State.GRID_BOOT && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "readFromNBT", at = @At("TAIL"))
|
||||||
|
private void eap$afterReadFromNBT(CompoundTag extra, net.minecraft.core.HolderLookup.Provider registries, CallbackInfo ci) {
|
||||||
|
// 从NBT加载后也重新初始化频道链接(只在服务端)
|
||||||
|
if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
// 从NBT加载时重置频道缓存,强制重新初始化
|
||||||
|
eap$lastChannel = -1;
|
||||||
|
eap$initializeChannelLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
public void eap$initializeChannelLink() {
|
||||||
|
// 防止重复调用
|
||||||
|
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
IUpgradeInventory inv = this.getUpgrades();
|
||||||
|
long channel = 0L;
|
||||||
|
boolean found = false;
|
||||||
|
for (var stack : inv) {
|
||||||
|
if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) {
|
||||||
|
channel = ChannelCardItem.getChannel(stack);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 频道没有变化则跳过
|
||||||
|
if (eap$lastChannel == channel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eap$lastChannel = channel;
|
||||||
|
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.setFrequency(0L);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
// 通知客户端状态变化
|
||||||
|
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eap$link == null) {
|
||||||
|
var endpoint = new GenericNodeEndpointImpl(
|
||||||
|
() -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(),
|
||||||
|
() -> ((IActionHost)(Object)this).getActionableNode()
|
||||||
|
);
|
||||||
|
eap$link = new WirelessSlaveLink(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
eap$link.setFrequency(channel);
|
||||||
|
eap$link.updateStatus();
|
||||||
|
|
||||||
|
// 通知客户端状态变化
|
||||||
|
((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ExtendedAELogger.LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$updateWirelessLink() {
|
||||||
|
if (eap$link != null) {
|
||||||
|
eap$link.updateStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean eap$isWirelessConnected() {
|
||||||
|
if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) {
|
||||||
|
return eap$clientConnected;
|
||||||
|
} else {
|
||||||
|
return eap$link != null && eap$link.isConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eap$setClientWirelessState(boolean connected) {
|
||||||
|
eap$clientConnected = connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -339,7 +339,7 @@ public abstract class GuiExPatternProviderMixin extends PatternProviderScreen<Co
|
||||||
|
|
||||||
// 定位到 GUI 右缘外侧一点(使用绝对屏幕坐标)
|
// 定位到 GUI 右缘外侧一点(使用绝对屏幕坐标)
|
||||||
int bx = this.leftPos + this.imageWidth + 1; // 向右平移 1px 到面板外侧
|
int bx = this.leftPos + this.imageWidth + 1; // 向右平移 1px 到面板外侧
|
||||||
int by = this.topPos + 20;
|
int by = this.topPos + 50;
|
||||||
int spacing = 22;
|
int spacing = 22;
|
||||||
// 翻页按钮交由左侧工具栏布局,无需手动定位
|
// 翻页按钮交由左侧工具栏布局,无需手动定位
|
||||||
if (this.divideBy2Button != null) {
|
if (this.divideBy2Button != null) {
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,6 @@ public class ScalePatternsC2SPacket implements CustomPacketPayload {
|
||||||
factor, result.getTotalPatterns(), result.getScaledPatterns(), result.getFailedPatterns());
|
factor, result.getTotalPatterns(), result.getScaledPatterns(), result.getFailedPatterns());
|
||||||
player.displayClientMessage(net.minecraft.network.chat.Component.literal("[EAP] " + summary), true);
|
player.displayClientMessage(net.minecraft.network.chat.Component.literal("[EAP] " + summary), true);
|
||||||
|
|
||||||
for (String err : result.getErrors()) {
|
|
||||||
ExtendedAELogger.LOGGER.debug("[EAP] scale error: {}", err);
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
ExtendedAELogger.LOGGER.error("[EAP] Handle ScalePatternsC2SPacket failed", t);
|
ExtendedAELogger.LOGGER.error("[EAP] Handle ScalePatternsC2SPacket failed", t);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import appeng.api.config.YesNo;
|
||||||
import appeng.menu.implementations.PatternProviderMenu;
|
import appeng.menu.implementations.PatternProviderMenu;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
||||||
|
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
|
||||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.codec.StreamCodec;
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
|
@ -12,6 +13,7 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C2S:切换高级阻挡模式。
|
* C2S:切换高级阻挡模式。
|
||||||
|
|
@ -36,16 +38,31 @@ public class ToggleAdvancedBlockingC2SPacket implements CustomPacketPayload {
|
||||||
public static void handle(final ToggleAdvancedBlockingC2SPacket msg, final IPayloadContext ctx) {
|
public static void handle(final ToggleAdvancedBlockingC2SPacket msg, final IPayloadContext ctx) {
|
||||||
ctx.enqueueWork(() -> {
|
ctx.enqueueWork(() -> {
|
||||||
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
||||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
var containerMenu = player.containerMenu;
|
||||||
|
if (containerMenu instanceof PatternProviderMenu menu) {
|
||||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||||
var logic = accessor.eap$logic();
|
var logic = accessor.eap$logic();
|
||||||
if (logic instanceof AdvancedBlockingHolder holder) {
|
if (logic instanceof AdvancedBlockingHolder holder) {
|
||||||
boolean next = !holder.eap$getAdvancedBlocking();
|
boolean current = holder.eap$getAdvancedBlocking();
|
||||||
holder.eap$setAdvancedBlocking(next);
|
boolean next = !current;
|
||||||
// 自动开启原版阻挡
|
holder.eap$setAdvancedBlocking(next);
|
||||||
logic.getConfigManager().putSetting(Settings.BLOCKING_MODE, YesNo.YES);
|
// 自动开启原版阻挡
|
||||||
logic.saveChanges();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.extendedae_plus.network;
|
||||||
|
|
||||||
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
|
import com.extendedae_plus.ae.menu.EntitySpeedTickerMenu;
|
||||||
|
import com.extendedae_plus.ae.parts.EntitySpeedTickerPart;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C2S: Toggle the accelerateEnabled flag on the EntitySpeedTickerPart bound to the open menu.
|
||||||
|
*/
|
||||||
|
public class ToggleEntityTickerC2SPacket implements CustomPacketPayload {
|
||||||
|
public static final CustomPacketPayload.Type<ToggleEntityTickerC2SPacket> TYPE = new CustomPacketPayload.Type<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "toggle_entity_ticker"));
|
||||||
|
|
||||||
|
public static final ToggleEntityTickerC2SPacket INSTANCE = new ToggleEntityTickerC2SPacket();
|
||||||
|
|
||||||
|
public static final StreamCodec<FriendlyByteBuf, ToggleEntityTickerC2SPacket> STREAM_CODEC =
|
||||||
|
StreamCodec.unit(INSTANCE);
|
||||||
|
|
||||||
|
private ToggleEntityTickerC2SPacket() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handle(final ToggleEntityTickerC2SPacket msg, final IPayloadContext ctx) {
|
||||||
|
ctx.enqueueWork(() -> {
|
||||||
|
if (!(ctx.player() instanceof ServerPlayer player)) 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.extendedae_plus.network;
|
||||||
import appeng.menu.implementations.PatternProviderMenu;
|
import appeng.menu.implementations.PatternProviderMenu;
|
||||||
import com.extendedae_plus.ExtendedAEPlus;
|
import com.extendedae_plus.ExtendedAEPlus;
|
||||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||||
|
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderMenuAdvancedAccessor;
|
||||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.network.codec.StreamCodec;
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
|
@ -10,6 +11,7 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import net.pedroksl.advanced_ae.gui.advpatternprovider.AdvPatternProviderMenu;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C2S:切换智能翻倍启用状态。
|
* C2S:切换智能翻倍启用状态。
|
||||||
|
|
@ -34,14 +36,25 @@ public class ToggleSmartDoublingC2SPacket implements CustomPacketPayload {
|
||||||
public static void handle(final ToggleSmartDoublingC2SPacket msg, final IPayloadContext ctx) {
|
public static void handle(final ToggleSmartDoublingC2SPacket msg, final IPayloadContext ctx) {
|
||||||
ctx.enqueueWork(() -> {
|
ctx.enqueueWork(() -> {
|
||||||
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
if (!(ctx.player() instanceof ServerPlayer player)) return;
|
||||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
var containerMenu = player.containerMenu;
|
||||||
|
if (containerMenu instanceof PatternProviderMenu menu) {
|
||||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||||
var logic = accessor.eap$logic();
|
var logic = accessor.eap$logic();
|
||||||
if (logic instanceof SmartDoublingHolder holder) {
|
if (logic instanceof SmartDoublingHolder holder) {
|
||||||
boolean next = !holder.eap$getSmartDoubling();
|
boolean current = holder.eap$getSmartDoubling();
|
||||||
holder.eap$setSmartDoubling(next);
|
boolean next = !current;
|
||||||
logic.saveChanges();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -540,7 +540,7 @@ public class ExtendedAEPatternUploadUtil {
|
||||||
var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class);
|
var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class);
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) {
|
for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) {
|
||||||
if (tile != null && tile.isFormed() && tile.getMainNode().isActive()) {
|
if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) {
|
||||||
var inv = tile.getExposedInventory();
|
var inv = tile.getExposedInventory();
|
||||||
if (inv != null) {
|
if (inv != null) {
|
||||||
result.add(inv);
|
result.add(inv);
|
||||||
|
|
@ -1206,4 +1206,26 @@ public class ExtendedAEPatternUploadUtil {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断给定矩阵集群中是否存在"装配矩阵上传核心"。
|
||||||
|
* 要求:至少存在 1 个即可,不限制数量。
|
||||||
|
* 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。
|
||||||
|
*/
|
||||||
|
private static boolean clusterHasSingleUploadCore(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixBase any) {
|
||||||
|
try {
|
||||||
|
if (any == null || any.getCluster() == null) return false;
|
||||||
|
int cores = 0;
|
||||||
|
var it = any.getCluster().getBlockEntities();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var te = it.next();
|
||||||
|
if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) {
|
||||||
|
cores++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cores >= 1; // 至少一个即可
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java
Normal file
41
src/main/java/com/extendedae_plus/util/FluxEnergyHelper.java
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.extendedae_plus.util;
|
||||||
|
|
||||||
|
import appeng.api.config.Actionable;
|
||||||
|
import appeng.api.networking.energy.IEnergyService;
|
||||||
|
import appeng.api.networking.security.IActionSource;
|
||||||
|
import appeng.api.storage.MEStorage;
|
||||||
|
import appeng.api.storage.StorageHelper;
|
||||||
|
import com.glodblock.github.appflux.common.me.key.FluxKey;
|
||||||
|
import com.glodblock.github.appflux.common.me.key.type.EnergyType;
|
||||||
|
|
||||||
|
public class FluxEnergyHelper {
|
||||||
|
/**
|
||||||
|
* 尝试从 ME 存储提取 FE 能量。
|
||||||
|
* @param energyService AE2 能量服务
|
||||||
|
* @param storage ME 存储
|
||||||
|
* @param feRequired 所需 FE 量
|
||||||
|
* @param source 操作来源
|
||||||
|
* @return 提取的 FE 量
|
||||||
|
*/
|
||||||
|
public static long extractFE(
|
||||||
|
IEnergyService energyService,
|
||||||
|
MEStorage storage,
|
||||||
|
long feRequired,
|
||||||
|
IActionSource source
|
||||||
|
) {
|
||||||
|
FluxKey feKey = FluxKey.of(EnergyType.FE);
|
||||||
|
|
||||||
|
// 模拟提取 FE
|
||||||
|
long feExtracted = StorageHelper.poweredExtraction(
|
||||||
|
energyService, storage, feKey, feRequired, source, Actionable.SIMULATE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 执行实际提取
|
||||||
|
if (feExtracted >= feRequired) {
|
||||||
|
return StorageHelper.poweredExtraction(
|
||||||
|
energyService, storage, feKey, feRequired, source, Actionable.MODULATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main/java/com/extendedae_plus/util/IStyleAccessor.java
Normal file
14
src/main/java/com/extendedae_plus/util/IStyleAccessor.java
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.extendedae_plus.util;
|
||||||
|
|
||||||
|
import appeng.client.gui.style.Blitter;
|
||||||
|
import appeng.client.gui.style.WidgetStyle;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IStyleAccessor {
|
||||||
|
|
||||||
|
Map<String, Blitter> getImages();
|
||||||
|
|
||||||
|
Map<String, WidgetStyle> getWidgets();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.extendedae_plus.util;
|
package com.extendedae_plus.util;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数字格式化工具类,提供大数字和小数的格式化功能
|
* 数字格式化工具类,提供大数字和小数的格式化功能
|
||||||
*/
|
*/
|
||||||
|
|
@ -15,13 +17,18 @@ public class NumberFormatUtil {
|
||||||
public static String formatNumber(long number) {
|
public static String formatNumber(long number) {
|
||||||
if (number < 1000) {
|
if (number < 1000) {
|
||||||
return String.valueOf(number);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,16 +38,24 @@ public class NumberFormatUtil {
|
||||||
*/
|
*/
|
||||||
public static String formatNumberWithDecimal(double value) {
|
public static String formatNumberWithDecimal(double value) {
|
||||||
if (value < 1000) {
|
if (value < 1000) {
|
||||||
|
DecimalFormat smallDf = new DecimalFormat("#.##");
|
||||||
|
// 小于1000时,若是整数则显示整数,否则显示最多两位小数
|
||||||
if (value == (long) value) {
|
if (value == (long) value) {
|
||||||
return String.valueOf((long) value);
|
return String.valueOf((long) value);
|
||||||
} else {
|
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,7 +69,7 @@ public class NumberFormatUtil {
|
||||||
if (Math.abs(value - Math.round(value)) < 0.001) {
|
if (Math.abs(value - Math.round(value)) < 0.001) {
|
||||||
return String.valueOf(Math.round(value)) + suffix;
|
return String.valueOf(Math.round(value)) + suffix;
|
||||||
} else {
|
} else {
|
||||||
// 修复重复后缀问题:先格式化数字,再添加后缀
|
// 使用一位小数显示,去掉末尾 .0
|
||||||
String formatted = String.format("%.1f", value).replaceAll("\\.0$", "");
|
String formatted = String.format("%.1f", value).replaceAll("\\.0$", "");
|
||||||
return formatted + suffix;
|
return formatted + suffix;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,6 @@ public class PatternProviderDataUtil {
|
||||||
patternDataList.add(new PatternData(patternDetails, patternStack, i));
|
patternDataList.add(new PatternData(patternDetails, patternStack, i));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) LogUtils.getLogger().debug("Pattern decode failed at slot {}: {}", i, e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -572,7 +571,6 @@ public class PatternProviderDataUtil {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) LogUtils.getLogger().debug("findSlotForPattern decode failed at {}: {}", i, e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
package com.extendedae_plus.util.entitySpeed;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置解析工具类:用于解析黑名单与倍率配置的字符串
|
||||||
|
*/
|
||||||
|
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 语法('*' 和 '?')以及完整的正则表达式。
|
||||||
|
*/
|
||||||
|
public static Pattern compilePattern(String raw) {
|
||||||
|
if (raw == null || raw.trim().isEmpty()) {
|
||||||
|
LOGGER.warn("Invalid pattern: {}", raw);
|
||||||
|
throw new IllegalArgumentException("Pattern is null or empty");
|
||||||
|
}
|
||||||
|
raw = raw.trim();
|
||||||
|
|
||||||
|
// Try regex first if it contains regex metacharacters
|
||||||
|
if (raw.contains(".*") || raw.matches(".*[\\[\\(\\+\\{\\\\].*")) {
|
||||||
|
try {
|
||||||
|
return Pattern.compile("^" + raw + "$");
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
LOGGER.warn("Failed to compile regex pattern '{}': {}", raw, e.getMessage());
|
||||||
|
// Fallback to glob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert glob to regex
|
||||||
|
if (raw.contains("*") || raw.contains("?")) {
|
||||||
|
StringBuilder sb = new StringBuilder("^");
|
||||||
|
for (char c : raw.toCharArray()) {
|
||||||
|
switch (c) {
|
||||||
|
case '*': sb.append(".*"); break;
|
||||||
|
case '?': sb.append('.'); break;
|
||||||
|
default:
|
||||||
|
if (".\\+[]{}()^$|".indexOf(c) >= 0) {
|
||||||
|
sb.append('\\');
|
||||||
|
}
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append('$');
|
||||||
|
return Pattern.compile(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Literal match
|
||||||
|
return Pattern.compile("^" + Pattern.quote(raw) + "$");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析倍率条目,如 'modid:block 2x'。
|
||||||
|
*/
|
||||||
|
public static MultiplierEntry parseMultiplierEntry(String entry) {
|
||||||
|
if (entry == null || entry.trim().isEmpty()) return null;
|
||||||
|
String[] parts = entry.trim().split("\\s+");
|
||||||
|
if (parts.length < 2) {
|
||||||
|
LOGGER.warn("Invalid multiplier entry: {}", entry);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String key = parts[0];
|
||||||
|
String val = parts[1].toLowerCase();
|
||||||
|
if (val.endsWith("x")) val = val.substring(0, val.length() - 1);
|
||||||
|
double multiplier;
|
||||||
|
try {
|
||||||
|
multiplier = Double.parseDouble(val);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
LOGGER.warn("Invalid multiplier value in '{}': {}", entry, val);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Pattern pattern = compilePattern(key);
|
||||||
|
return new MultiplierEntry(pattern, multiplier);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.warn("Failed to compile pattern in '{}': {}", entry, e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查方块是否在黑名单中。
|
||||||
|
*/
|
||||||
|
public static boolean isBlockBlacklisted(String blockId, List<? extends String> blacklist) {
|
||||||
|
if (blockId == null) return false;
|
||||||
|
return getCachedBlacklist(blacklist).stream().anyMatch(p -> p.matcher(blockId).matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取方块的倍率。
|
||||||
|
*/
|
||||||
|
public static double getMultiplierForBlock(String blockId, List<? extends String> multipliers) {
|
||||||
|
if (blockId == null) return 1.0;
|
||||||
|
double maxMultiplier = 1.0;
|
||||||
|
for (MultiplierEntry me : getCachedMultiplierEntries(multipliers)) {
|
||||||
|
if (me.pattern.matcher(blockId).matches()) {
|
||||||
|
maxMultiplier = Math.max(maxMultiplier, me.multiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Pattern> compilePatterns(List<? extends String> raw) {
|
||||||
|
List<Pattern> out = new ArrayList<>();
|
||||||
|
if (raw == null) return out;
|
||||||
|
for (String s : raw) {
|
||||||
|
if (s == null || s.trim().isEmpty()) continue;
|
||||||
|
try {
|
||||||
|
out.add(compilePattern(s));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.warn("Failed to compile pattern '{}': {}", s, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 volatile List<String> cachedBlacklistSourceSnapshot = null;
|
||||||
|
private static volatile List<String> cachedMultiplierSourceSnapshot = null;
|
||||||
|
private static final Object CACHE_LOCK = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已解析并缓存的黑名单(线程安全、懒加载)。
|
||||||
|
*/
|
||||||
|
public static List<Pattern> getCachedBlacklist(List<? extends String> source) {
|
||||||
|
List<String> normalized = normalizeSource(source);
|
||||||
|
if (cachedBlacklist != null && listEquals(cachedBlacklistSourceSnapshot, normalized)) {
|
||||||
|
return Collections.unmodifiableList(cachedBlacklist);
|
||||||
|
}
|
||||||
|
synchronized (CACHE_LOCK) {
|
||||||
|
if (cachedBlacklist == null || !listEquals(cachedBlacklistSourceSnapshot, normalized)) {
|
||||||
|
cachedBlacklist = compilePatterns(normalized);
|
||||||
|
cachedBlacklistSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(cachedBlacklist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取已解析并缓存的倍率列表(线程安全、懒加载)。
|
||||||
|
*/
|
||||||
|
public static List<MultiplierEntry> getCachedMultiplierEntries(List<? extends String> source) {
|
||||||
|
List<String> normalized = normalizeSource(source);
|
||||||
|
if (cachedMultiplierEntries != null && listEquals(cachedMultiplierSourceSnapshot, normalized)) {
|
||||||
|
return Collections.unmodifiableList(cachedMultiplierEntries);
|
||||||
|
}
|
||||||
|
synchronized (CACHE_LOCK) {
|
||||||
|
if (cachedMultiplierEntries == null || !listEquals(cachedMultiplierSourceSnapshot, normalized)) {
|
||||||
|
cachedMultiplierEntries = parseMultiplierList(normalized);
|
||||||
|
cachedMultiplierSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(cachedMultiplierEntries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the incoming source list: trim entries, drop blanks, keep stable ordering
|
||||||
|
private static List<String> normalizeSource(List<? extends String> source) {
|
||||||
|
List<String> out = new ArrayList<>();
|
||||||
|
if (source == null) return out;
|
||||||
|
for (String s : source) {
|
||||||
|
if (s == null) continue;
|
||||||
|
String t = s.trim();
|
||||||
|
if (t.isEmpty()) continue;
|
||||||
|
out.add(t);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null-safe equality for two lists of strings. Uses size + element equals.
|
||||||
|
private static boolean listEquals(List<String> a, List<String> b) {
|
||||||
|
if (a == b) return true;
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
if (a.size() != b.size()) return false;
|
||||||
|
for (int i = 0; i < a.size(); i++) {
|
||||||
|
if (!a.get(i).equals(b.get(i))) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空缓存,下一次获取时将重新从提供的源解析(或调用方可以重新调用 getter)。
|
||||||
|
*/
|
||||||
|
public static void reload() {
|
||||||
|
synchronized (CACHE_LOCK) {
|
||||||
|
cachedBlacklist = null;
|
||||||
|
cachedMultiplierEntries = null;
|
||||||
|
cachedBlacklistSourceSnapshot = null;
|
||||||
|
cachedMultiplierSourceSnapshot = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.extendedae_plus.util.entitySpeed;
|
||||||
|
|
||||||
|
import appeng.api.upgrades.IUpgradeInventory;
|
||||||
|
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
|
||||||
|
import com.extendedae_plus.config.ModConfigs;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于计算实体加速器的能耗与加速倍率的工具类
|
||||||
|
*/
|
||||||
|
public final class PowerUtils {
|
||||||
|
private PowerUtils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算加速卡的乘积并应用上限。
|
||||||
|
* @param upgrades 升级槽位
|
||||||
|
* @param maxCards 最大计入的卡数
|
||||||
|
* @return 被上限约束后的乘积
|
||||||
|
*/
|
||||||
|
public static long computeProductWithCap(IUpgradeInventory upgrades, int maxCards) {
|
||||||
|
List<Integer> multipliers = new ArrayList<>();
|
||||||
|
int considered = 0;
|
||||||
|
for (ItemStack stack : upgrades) {
|
||||||
|
if (considered >= maxCards) break;
|
||||||
|
if (stack != null && !stack.isEmpty() && stack.getItem() instanceof EntitySpeedCardItem) {
|
||||||
|
int multVal = EntitySpeedCardItem.readMultiplier(stack);
|
||||||
|
int count = Math.min(stack.getCount(), maxCards - considered);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
multipliers.add(multVal);
|
||||||
|
considered++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long product = 1L;
|
||||||
|
int highest = 1;
|
||||||
|
for (Integer m : multipliers) {
|
||||||
|
if (m == null || m <= 0) continue;
|
||||||
|
product *= m;
|
||||||
|
highest = Math.max(highest, m);
|
||||||
|
}
|
||||||
|
return Math.min(product, capForHighestMultiplier(highest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据最高单卡倍率返回上限值。
|
||||||
|
*/
|
||||||
|
public static long capForHighestMultiplier(int highestMultiplier) {
|
||||||
|
if (highestMultiplier >= 16) return 1024L;
|
||||||
|
if (highestMultiplier >= 8) return 256L;
|
||||||
|
if (highestMultiplier >= 4) return 64L;
|
||||||
|
if (highestMultiplier >= 2) return 8L;
|
||||||
|
return 1L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算最终能耗。
|
||||||
|
* @param product 加速卡乘积
|
||||||
|
* @param energyCardCount 能量卡数量
|
||||||
|
* @return 最终能耗值
|
||||||
|
*/
|
||||||
|
public static double computeFinalPowerForProduct(long product, int energyCardCount) {
|
||||||
|
if (product <= 1L) return 0.0;
|
||||||
|
double base = ModConfigs.ENTITY_TICKER_COST.get();
|
||||||
|
double log2 = Math.log(product) / Math.log(2.0);
|
||||||
|
double raw;
|
||||||
|
if (product == 2L) {
|
||||||
|
raw = base * 4;
|
||||||
|
} else if (product <= 256L) {
|
||||||
|
raw = base * Math.pow(2.0, 1.5 * log2) * 2;
|
||||||
|
} else {
|
||||||
|
raw = base * Math.pow(2.0, 2.5 * log2);
|
||||||
|
}
|
||||||
|
return raw * getRemainingRatio(energyCardCount) / 8.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算能量卡减免后的剩余功耗比率。
|
||||||
|
*/
|
||||||
|
public static double getRemainingRatio(int energyCardCount) {
|
||||||
|
if (energyCardCount <= 0) return 1.0;
|
||||||
|
if (energyCardCount == 1) return 0.9;
|
||||||
|
if (energyCardCount >= 8) return 0.5;
|
||||||
|
return 1.0 - 0.5 * (1.0 - Math.pow(0.7, energyCardCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将剩余功耗比率格式化为百分比字符串。
|
||||||
|
*/
|
||||||
|
public static String formatPercentage(double ratio) {
|
||||||
|
double pct = ratio * 100.0;
|
||||||
|
return Math.abs(pct - Math.round(pct)) < 1e-9 ?
|
||||||
|
String.format("%d%%", Math.round(pct)) :
|
||||||
|
String.format("%.2f%%", pct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.extendedae_plus.util.storage;
|
||||||
|
|
||||||
|
public interface InfinityConstants {
|
||||||
|
// 当前磁盘格式版本号,增加字段用于向后/向前兼容
|
||||||
|
int FORMAT_VERSION = 2;
|
||||||
|
// 存储磁盘数据的格式版本号
|
||||||
|
String FORMAT_VERSION_FIELD = "infinity_format_version";
|
||||||
|
|
||||||
|
// savedData 文件名常量
|
||||||
|
String SAVE_FILE_NAME = "infinity_biginteger_cells";
|
||||||
|
|
||||||
|
// 磁盘的唯一标识符键名,存储在 ItemStack 和 InfinityStorageManager 中
|
||||||
|
String INFINITY_CELL_UUID = "infinity_cell_uuid";
|
||||||
|
// 单个磁盘的 InfinityDataStorage 数据键名
|
||||||
|
String INFINITY_CELL_DATA = "infinity_cell_data";
|
||||||
|
// 所有磁盘数据的列表键名,存储在 InfinityStorageManager 的 NBT 中
|
||||||
|
String INFINITY_CELL_LIST = "infinity_cell_list";
|
||||||
|
|
||||||
|
// 磁盘中所有物品键的键名(ListTag of CompoundTag)
|
||||||
|
String INFINITY_CELL_KEYS = "infinity_cell_keys";
|
||||||
|
// 磁盘中每种物品数量的键名(ListTag of CompoundTag,包含 "value")
|
||||||
|
String INFINITY_CELL_AMOUNTS = "infinity_cell_amounts";
|
||||||
|
// 磁盘中所有物品的总数键名(ListTag,包含一个 CompoundTag 的 "value")
|
||||||
|
String INFINITY_CELL_ITEM_COUNT = "infinity_cell_item_count";
|
||||||
|
|
||||||
|
// ItemStack 的 NBT 中存储总物品数量的键名
|
||||||
|
String INFINITY_ITEM_TOTAL = "infinity_item_total";
|
||||||
|
// ItemStack 的 NBT 中存储物品种类数量的键名
|
||||||
|
String INFINITY_ITEM_TYPES = "infinity_item_types";
|
||||||
|
}
|
||||||
|
|
@ -2,60 +2,47 @@ package com.extendedae_plus.util.storage;
|
||||||
|
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
import net.minecraft.nbt.ListTag;
|
import net.minecraft.nbt.ListTag;
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InfinityDataStorage
|
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||||
*
|
* Original copyright (c) Technici4n<p>
|
||||||
* 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。
|
|
||||||
* 数据结构说明:
|
|
||||||
* - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目
|
|
||||||
* - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示:
|
|
||||||
* - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值
|
|
||||||
* - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式
|
|
||||||
*
|
|
||||||
* 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。
|
|
||||||
*/
|
*/
|
||||||
public class InfinityDataStorage {
|
public class InfinityDataStorage {
|
||||||
|
// 定义一个静态常量 EMPTY,表示一个空的 DataStorage 实例,用于默认或占位场景
|
||||||
/** 空实例(表示没有数据) */
|
|
||||||
public static final InfinityDataStorage EMPTY = new InfinityDataStorage();
|
public static final InfinityDataStorage EMPTY = new InfinityDataStorage();
|
||||||
|
|
||||||
/** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */
|
|
||||||
public ListTag keys;
|
public ListTag keys;
|
||||||
/**
|
|
||||||
* 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag):
|
|
||||||
* - 若数量能放入 long,则 CompoundTag 包含键 "l"(long)
|
|
||||||
* - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式
|
|
||||||
*/
|
|
||||||
public ListTag amounts;
|
public ListTag amounts;
|
||||||
|
// 存储磁盘中物品的总数,使用 BigInteger 支持大容量
|
||||||
|
public BigInteger itemCount;
|
||||||
|
|
||||||
public InfinityDataStorage() {
|
public InfinityDataStorage() {
|
||||||
this(new ListTag(), new ListTag());
|
this(new ListTag(), new ListTag(), BigInteger.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InfinityDataStorage(ListTag keys, ListTag amounts) {
|
private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) {
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.amounts = amounts;
|
this.amounts = amounts;
|
||||||
|
this.itemCount = itemCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 将 DataStorage 数据序列化为 NBT 格式
|
||||||
* 将当前数据封装为 CompoundTag 以写入存档
|
|
||||||
*/
|
|
||||||
public CompoundTag serializeNBT() {
|
public CompoundTag serializeNBT() {
|
||||||
CompoundTag nbt = new CompoundTag();
|
CompoundTag nbt = new CompoundTag();
|
||||||
nbt.put("keys", keys);
|
nbt.put(InfinityConstants.INFINITY_CELL_KEYS, keys);
|
||||||
nbt.put("amounts", amounts);
|
nbt.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts);
|
||||||
|
nbt.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray());
|
||||||
return nbt;
|
return nbt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 从 NBT 数据反序列化创建 DataStorage 实例
|
||||||
* 从存档读取数据并构造实例
|
|
||||||
*/
|
|
||||||
public static InfinityDataStorage loadFromNBT(CompoundTag nbt) {
|
public static InfinityDataStorage loadFromNBT(CompoundTag nbt) {
|
||||||
ListTag stackKeys = nbt.getList("keys", Tag.TAG_COMPOUND);
|
ListTag keys = nbt.getList(InfinityConstants.INFINITY_CELL_KEYS, ListTag.TAG_COMPOUND);
|
||||||
// amounts 以 CompoundTag 列表存储,每个 CompoundTag 内含 long 或 String
|
ListTag amounts = nbt.getList(InfinityConstants.INFINITY_CELL_AMOUNTS, ListTag.TAG_COMPOUND);
|
||||||
ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND);
|
BigInteger itemCount = new BigInteger(nbt.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT));
|
||||||
return new InfinityDataStorage(stackKeys, stackAmounts);
|
// 使用加载的数据创建新的 DataStorage 实例
|
||||||
|
return new InfinityDataStorage(keys, amounts, itemCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,74 +6,133 @@ import net.minecraft.nbt.ListTag;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.saveddata.SavedData;
|
import net.minecraft.world.level.saveddata.SavedData;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.HashMap;
|
import java.math.BigInteger;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InfinityStorageManager
|
* This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.<p>
|
||||||
* <p>
|
* Original copyright (c) Technici4n<p>
|
||||||
* 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
|
|
||||||
* 功能要点:
|
|
||||||
* - 在世界加载时从存档恢复所有 cell 的数据
|
|
||||||
* - 提供按 UUID 获取/创建单个 cell 的数据容器
|
|
||||||
* - 在世界保存时将内存数据打包为 NBT 写回存档
|
|
||||||
*/
|
*/
|
||||||
public class InfinityStorageManager extends SavedData {
|
public class InfinityStorageManager extends SavedData {
|
||||||
private static final Factory<InfinityStorageManager> FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt);
|
private static final Factory<InfinityStorageManager> FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt);
|
||||||
|
// 存储所有磁盘的Map,键为UUID,值为DataStorage对象
|
||||||
/**
|
|
||||||
* 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;
|
private final Map<UUID, InfinityDataStorage> cells;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private WeakReference<HolderLookup.Provider> registries;
|
private WeakReference<HolderLookup.Provider> registries;
|
||||||
|
|
||||||
|
|
||||||
|
// 构造方法,初始化磁盘Map
|
||||||
public InfinityStorageManager() {
|
public InfinityStorageManager() {
|
||||||
this.cells = new HashMap<>();
|
cells = new HashMap<>();
|
||||||
setDirty();
|
// 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
|
||||||
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfinityStorageManager(Map<UUID, InfinityDataStorage> cells) {
|
// 私有构造方法,用于从已有Map创建StorageManager
|
||||||
|
private InfinityStorageManager(Map<UUID, InfinityDataStorage> cells) {
|
||||||
|
// 确保使用已加载的数据
|
||||||
this.cells = cells;
|
this.cells = cells;
|
||||||
setDirty();
|
// 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
|
||||||
|
this.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 NBT 构造:用于在世界加载时从存档恢复数据
|
// 静态方法,从 NBT 数据反序列化创建 StorageManager 实例
|
||||||
*/
|
public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) {
|
||||||
public InfinityStorageManager(CompoundTag nbt) {
|
// 读取格式版本,缺省视为 1(兼容旧档)
|
||||||
this.cells = new HashMap<>();
|
int version = nbt.contains(InfinityConstants.FORMAT_VERSION_FIELD) ?
|
||||||
ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
|
nbt.getInt(InfinityConstants.FORMAT_VERSION_FIELD) :
|
||||||
|
1;
|
||||||
|
|
||||||
|
Map<UUID, InfinityDataStorage> cells = new HashMap<>();
|
||||||
|
// 从 NBT 中获取磁盘数据列表,指定类型为 CompoundTag(TAG_COMPOUND)
|
||||||
|
ListTag cellList = nbt.getList(InfinityConstants.INFINITY_CELL_LIST, CompoundTag.TAG_COMPOUND);
|
||||||
|
// 遍历 cellList 中的每个 CompoundTag
|
||||||
for (int i = 0; i < cellList.size(); i++) {
|
for (int i = 0; i < cellList.size(); i++) {
|
||||||
|
// 获取当前索引的 CompoundTag,表示单个磁盘的数据
|
||||||
CompoundTag cell = cellList.getCompound(i);
|
CompoundTag cell = cellList.getCompound(i);
|
||||||
cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data")));
|
// 从 CompoundTag 中读取 UUID 和 DataStorage 数据,并存入 cells 映射
|
||||||
|
cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA)));
|
||||||
}
|
}
|
||||||
|
// 使用加载的 cells 数据创建新的 StorageManager 实例
|
||||||
|
return new InfinityStorageManager(cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag save(CompoundTag nbt, HolderLookup.Provider provider) {
|
||||||
|
// 将内存中的所有 cell 序列化为一个 ListTag
|
||||||
|
ListTag cellList = new ListTag();
|
||||||
|
for (Map.Entry<UUID, InfinityDataStorage> entry : cells.entrySet()) {
|
||||||
|
CompoundTag cell = new CompoundTag();
|
||||||
|
cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey());
|
||||||
|
cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT());
|
||||||
|
cellList.add(cell);
|
||||||
|
}
|
||||||
|
nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList);
|
||||||
|
// 写入当前格式版本号,便于未来迁移与兼容判断
|
||||||
|
nbt.putInt(InfinityConstants.FORMAT_VERSION_FIELD, InfinityConstants.FORMAT_VERSION);
|
||||||
|
return nbt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途
|
||||||
|
public Set<UUID> getAllLoadedUUIDs() {
|
||||||
|
return Collections.unmodifiableSet(cells.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
|
||||||
|
public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
|
||||||
|
cells.put(uuid, infinityDataStorage);
|
||||||
|
// 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘
|
||||||
setDirty();
|
setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 删除某个 UUID 的持久化记录并标记为脏
|
||||||
* 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE
|
public void removeCell(UUID uuid) {
|
||||||
*/
|
cells.remove(uuid);
|
||||||
public static InfinityStorageManager getForLevel(ServerLevel level) {
|
// 标记数据为“脏”,确保移除操作会在下次保存时反映到磁盘
|
||||||
if (INSTANCE == null && level != null) {
|
setDirty();
|
||||||
INSTANCE = level.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME);
|
}
|
||||||
INSTANCE.registries = new WeakReference<>(level.registryAccess());
|
|
||||||
|
// 检查指定 UUID 是否存在于 disks 映射中
|
||||||
|
public boolean hasUUID(UUID uuid) {
|
||||||
|
// 返回 cells 映射是否包含指定 UUID
|
||||||
|
return cells.containsKey(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建某个 UUID 对应的数据容器
|
||||||
|
public InfinityDataStorage getOrCreateCell(UUID uuid) {
|
||||||
|
// 检查 cells 映射中是否不存在指定 UUID
|
||||||
|
if (!cells.containsKey(uuid)) {
|
||||||
|
updateCell(uuid, new InfinityDataStorage());
|
||||||
}
|
}
|
||||||
return INSTANCE;
|
// 返回指定 UUID 对应的 DataStorage 对象
|
||||||
|
return cells.get(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改指定 UUID 的磁盘数据,包括堆栈键、数量和总项目数
|
||||||
|
public void modifyDisk(UUID uuid, ListTag keys, ListTag amounts, BigInteger itemCount) {
|
||||||
|
// 获取或创建指定 UUID 的 DataStorage 对象
|
||||||
|
InfinityDataStorage cellToModify = getOrCreateCell(uuid);
|
||||||
|
if (keys != null && amounts != null) {
|
||||||
|
cellToModify.keys = keys;
|
||||||
|
cellToModify.amounts = amounts;
|
||||||
|
}
|
||||||
|
// 更新 DataStorage 的 itemCount 字段
|
||||||
|
cellToModify.itemCount = itemCount;
|
||||||
|
// 将修改后的 DataStorage 对象更新到 cells 映射
|
||||||
|
updateCell(uuid, cellToModify);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static InfinityStorageManager getInstance(MinecraftServer server) {
|
||||||
|
ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
|
||||||
|
var manager = world.getDataStorage().computeIfAbsent(FACTORY, InfinityConstants.SAVE_FILE_NAME);
|
||||||
|
manager.registries = new WeakReference<>(server.registryAccess());
|
||||||
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HolderLookup.Provider getRegistries() {
|
public HolderLookup.Provider getRegistries() {
|
||||||
|
|
@ -89,78 +148,4 @@ public class InfinityStorageManager extends SavedData {
|
||||||
|
|
||||||
return registries;
|
return registries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull CompoundTag save(@NotNull CompoundTag nbt, HolderLookup.@NotNull Provider registries) {
|
|
||||||
// 将内存中的所有 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) {
|
|
||||||
Map<UUID, InfinityDataStorage> cell = new HashMap<>();
|
|
||||||
ListTag diskList = nbt.getList("list", CompoundTag.TAG_COMPOUND);
|
|
||||||
for (int i = 0; i < diskList.size(); i++) {
|
|
||||||
CompoundTag disk = diskList.getCompound(i);
|
|
||||||
cell.put(disk.getUUID("uuid"), InfinityDataStorage.loadFromNBT(disk.getCompound("data")));
|
|
||||||
}
|
|
||||||
return new InfinityStorageManager(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InfinityStorageManager getInstance(MinecraftServer server) {
|
|
||||||
ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
|
|
||||||
InfinityStorageManager manager = null;
|
|
||||||
if (world != null) {
|
|
||||||
manager = world.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME);
|
|
||||||
}
|
|
||||||
if (manager != null) {
|
|
||||||
manager.registries = new WeakReference<>(server.registryAccess());
|
|
||||||
}
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.extendedae_plus.wireless;
|
package com.extendedae_plus.wireless;
|
||||||
|
|
||||||
import appeng.api.networking.GridHelper;
|
import appeng.api.networking.GridHelper;
|
||||||
|
import appeng.api.networking.IGridConnection;
|
||||||
import appeng.api.networking.IGridNode;
|
import appeng.api.networking.IGridNode;
|
||||||
import appeng.me.service.helpers.ConnectionWrapper;
|
import appeng.me.service.helpers.ConnectionWrapper;
|
||||||
import com.extendedae_plus.config.ModConfig;
|
import com.extendedae_plus.config.ModConfig;
|
||||||
|
import com.extendedae_plus.util.ExtendedAELogger;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -90,10 +92,16 @@ public class WirelessSlaveLink {
|
||||||
current.destroy();
|
current.destroy();
|
||||||
connection = new ConnectionWrapper(null);
|
connection = new ConnectionWrapper(null);
|
||||||
}
|
}
|
||||||
|
// AE2 侧是否已经存在连接(例如此前创建但 wrapper 丢失)
|
||||||
|
IGridConnection existing = findExistingConnection(a, b);
|
||||||
|
if (existing != null) {
|
||||||
|
connection = new ConnectionWrapper(existing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
connection = new ConnectionWrapper(GridHelper.createConnection(a, b));
|
connection = new ConnectionWrapper(GridHelper.createConnection(a, b));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException ignore) {
|
} catch (IllegalStateException ex) {
|
||||||
// 连接非法(如重复连接等)——落入重建/关闭逻辑
|
// 连接非法(如重复连接等)——落入重建/关闭逻辑
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -114,9 +122,58 @@ public class WirelessSlaveLink {
|
||||||
private void destroyConnection() {
|
private void destroyConnection() {
|
||||||
var current = connection.getConnection();
|
var current = connection.getConnection();
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
|
var a = current.a();
|
||||||
|
var b = current.b();
|
||||||
|
// 先销毁连接,再唤醒两端节点,使其尽快感知到状态变化
|
||||||
current.destroy();
|
current.destroy();
|
||||||
|
try {
|
||||||
|
if (a != null && a.getGrid() != null) {
|
||||||
|
a.getGrid().getTickManager().wakeDevice(a);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
try {
|
||||||
|
if (b != null && b.getGrid() != null) {
|
||||||
|
b.getGrid().getTickManager().wakeDevice(b);
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
connection.setConnection(null);
|
connection.setConnection(null);
|
||||||
|
} else {
|
||||||
|
// 兜底:如果 wrapper 已丢失,但 AE2 内仍有直连,则尝试查找并销毁
|
||||||
|
try {
|
||||||
|
IGridNode a = host.getGridNode();
|
||||||
|
if (a != null) {
|
||||||
|
for (IGridConnection gc : a.getConnections()) {
|
||||||
|
// 我们创建的是非 in-world 直连
|
||||||
|
if (gc != null && !gc.isInWorld()) {
|
||||||
|
IGridNode other = gc.getOtherSide(a);
|
||||||
|
Object owner = other != null ? other.getOwner() : null;
|
||||||
|
if (owner instanceof IWirelessEndpoint) {
|
||||||
|
gc.destroy();
|
||||||
|
try { if (a.getGrid() != null) { a.getGrid().getTickManager().wakeDevice(a); } } catch (Throwable ignored) {}
|
||||||
|
try { if (other != null && other.getGrid() != null) { other.getGrid().getTickManager().wakeDevice(other); } } catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
}
|
}
|
||||||
connection = new ConnectionWrapper(null);
|
connection = new ConnectionWrapper(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 AE2 中查找 a 与 b 之间是否已经有连接存在(用于复用,避免重复创建异常)。
|
||||||
|
*/
|
||||||
|
private IGridConnection findExistingConnection(IGridNode a, IGridNode b) {
|
||||||
|
try {
|
||||||
|
for (IGridConnection gc : a.getConnections()) {
|
||||||
|
var ga = gc.a();
|
||||||
|
var gb = gc.b();
|
||||||
|
if ((ga == a || gb == a) && (ga == b || gb == b)) {
|
||||||
|
return gc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.extendedae_plus.wireless.endpoint;
|
||||||
|
|
||||||
|
import appeng.api.networking.IGridNode;
|
||||||
|
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用 IWirelessEndpoint:通过提供方块实体与节点的 Supplier 实现。
|
||||||
|
*/
|
||||||
|
public class GenericNodeEndpointImpl implements IWirelessEndpoint {
|
||||||
|
private final Supplier<BlockEntity> blockEntitySupplier;
|
||||||
|
private final Supplier<IGridNode> nodeSupplier;
|
||||||
|
|
||||||
|
public GenericNodeEndpointImpl(Supplier<BlockEntity> blockEntitySupplier, Supplier<IGridNode> nodeSupplier) {
|
||||||
|
this.blockEntitySupplier = Objects.requireNonNull(blockEntitySupplier);
|
||||||
|
this.nodeSupplier = Objects.requireNonNull(nodeSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerLevel getServerLevel() {
|
||||||
|
var be = blockEntitySupplier.get();
|
||||||
|
if (be == null) return null;
|
||||||
|
Level lvl = be.getLevel();
|
||||||
|
return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getBlockPos() {
|
||||||
|
var be = blockEntitySupplier.get();
|
||||||
|
return be != null ? be.getBlockPos() : BlockPos.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IGridNode getGridNode() {
|
||||||
|
return nodeSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEndpointRemoved() {
|
||||||
|
var be = blockEntitySupplier.get();
|
||||||
|
return be == null || be.isRemoved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.extendedae_plus.wireless.endpoint;
|
||||||
|
|
||||||
|
import appeng.api.networking.IGridNode;
|
||||||
|
import appeng.helpers.InterfaceLogicHost;
|
||||||
|
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IWirelessEndpoint 实现:基于 InterfaceLogicHost 与节点提供者。
|
||||||
|
*/
|
||||||
|
public class InterfaceNodeEndpointImpl implements IWirelessEndpoint {
|
||||||
|
private final InterfaceLogicHost host;
|
||||||
|
private final Supplier<IGridNode> nodeSupplier;
|
||||||
|
|
||||||
|
public InterfaceNodeEndpointImpl(InterfaceLogicHost host, Supplier<IGridNode> nodeSupplier) {
|
||||||
|
this.host = Objects.requireNonNull(host);
|
||||||
|
this.nodeSupplier = Objects.requireNonNull(nodeSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServerLevel getServerLevel() {
|
||||||
|
var be = host.getBlockEntity();
|
||||||
|
if (be == null) return null;
|
||||||
|
Level lvl = be.getLevel();
|
||||||
|
return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockPos getBlockPos() {
|
||||||
|
var be = host.getBlockEntity();
|
||||||
|
return be != null ? be.getBlockPos() : BlockPos.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IGridNode getGridNode() {
|
||||||
|
return nodeSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEndpointRemoved() {
|
||||||
|
var be = host.getBlockEntity();
|
||||||
|
return be == null || be.isRemoved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"$schema": "schema.json",
|
||||||
|
"includes": [
|
||||||
|
"common/common.json",
|
||||||
|
"common/player_inventory.json",
|
||||||
|
"common/toolbox.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": 1,
|
||||||
|
"bottom": 55
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slots": {
|
||||||
|
"TOOLBOX": {
|
||||||
|
"right": 0,
|
||||||
|
"bottom": 50,
|
||||||
|
"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: 2.4 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"": { "model": "extendedae_plus:block/assembler_matrix_upload_core" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,57 +1,111 @@
|
||||||
{
|
{
|
||||||
"gui.expatternprovider.toggle_slots": "Toggle Slots",
|
"gui.expatternprovider.toggle_slots": "Toggle Slot Display",
|
||||||
"gui.expatternprovider.hide_slots": "Hide Slots",
|
"gui.expatternprovider.hide_slots": "Hide Slots",
|
||||||
"gui.expatternprovider.show_slots": "Show Slots",
|
"gui.expatternprovider.show_slots": "Show Slots",
|
||||||
"gui.expatternprovider.clear_selection": "Clear Selection",
|
"gui.expatternprovider.clear_selection": "Clear Selection",
|
||||||
"gui.extendedae_plus.pattern_provider.select": "Select Pattern Provider",
|
"gui.extendedae_plus.global.toggle_blocking": "Toggle Blocking Mode",
|
||||||
"gui.extendedae_plus.pattern_provider.selected": "Pattern Provider Selected",
|
"gui.extendedae_plus.global.toggle_adv_blocking": "Toggle Advanced Blocking",
|
||||||
"gui.extendedae_plus.pattern_provider.unselected": "Click to select this pattern provider",
|
"gui.extendedae_plus.global.toggle_smart_doubling": "Toggle Smart Doubling",
|
||||||
"message.extendedae_plus.no_provider_selected": "Please select a pattern provider first",
|
"gui.extendedae_plus.global.all_on": "All On",
|
||||||
"message.extendedae_plus.invalid_pattern": "Please hold a valid encoded pattern",
|
"gui.extendedae_plus.global.all_off": "All Off",
|
||||||
"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",
|
|
||||||
"itemGroup.extendedae_plus.main": "ExtendedAE Plus",
|
|
||||||
|
|
||||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Info",
|
"itemGroup.extendedae_plus.main": "ExtendedAE Plus",
|
||||||
"config.jade.plugin_extendedae_plus.wt_frequency": "Show Frequency",
|
"item.extendedae_plus.wireless_transceiver": "Wireless Transceiver",
|
||||||
"config.jade.plugin_extendedae_plus.wt_master_mode": "Show Master/Slave Mode",
|
"item.extendedae_plus.4x_crafting_accelerator": "4x Crafting Accelerator",
|
||||||
"config.jade.plugin_extendedae_plus.wt_master_location": "Show Master Position",
|
"item.extendedae_plus.16x_crafting_accelerator": "16x Crafting Accelerator",
|
||||||
"config.jade.plugin_extendedae_plus.wt_locked": "Show Locked State",
|
"item.extendedae_plus.64x_crafting_accelerator": "64x Crafting Accelerator",
|
||||||
"config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State",
|
"item.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator",
|
||||||
"extendedae_plus.tooltip.frequency": "Frequency: %d",
|
"item.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator",
|
||||||
"extendedae_plus.tooltip.master_mode": "Mode: %s",
|
"item.extendedae_plus.network_pattern_controller": "Pattern Provider Status Controller",
|
||||||
"extendedae_plus.tooltip.locked": "Locked: %s",
|
"item.extendedae_plus.assembler_matrix_upload_core": "Assembly Matrix Upload Core",
|
||||||
|
"item.extendedae_plus.channel_card": "Channel Card",
|
||||||
|
"item.extendedae_plus.channel_card.channel": "Channel: %d",
|
||||||
|
"item.extendedae_plus.channel_card.channel.unset": "Channel: Unset",
|
||||||
|
"item.extendedae_plus.channel_card.set": "Channel set to: %d",
|
||||||
|
"item.extendedae_plus.entity_speed_card": "Entity Speed Card",
|
||||||
|
"item.extendedae_plus.entity_speed_card.x2": "Entity Speed Card (x2)",
|
||||||
|
"item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
|
||||||
|
"item.extendedae_plus.entity_speed_card.x8": "Entity Speed Card (x8)",
|
||||||
|
"item.extendedae_plus.entity_speed_card.x16": "Entity Speed Card (x16)",
|
||||||
"item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence",
|
"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.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.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": "Maximum Effect: %s Times",
|
||||||
|
|
||||||
|
"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 Provider Status Controller",
|
||||||
|
"block.extendedae_plus.assembler_matrix_upload_core": "Assembly Matrix Upload Core",
|
||||||
|
|
||||||
|
"extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix",
|
||||||
|
"extendedae_plus.upload_to_matrix.success": "Pattern Uploaded to Assembly Matrix",
|
||||||
|
"extendedae_plus.upload_to_matrix.fail_not_crafting": "Only Crafting Patterns Can Be Uploaded, 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": "Assembly Matrix Pattern Storage Is Full or Cannot Be Inserted",
|
||||||
|
"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 Accelerator",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§lMachine Has Been Disabled",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.energy": "Energy Consumption: %s FE/t",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "Power Ratio: %s",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.speed": "Current Acceleration Multiplier: %d",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.multiplier": "Additional Consumption Multiplier: %s",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§lInsufficient network energy",
|
||||||
|
|
||||||
|
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "Requires Entity Speed Card to Enable Acceleration",
|
||||||
|
"item.extendedae_plus.entity_speed_ticker.tip.max": "Up to 1024x Acceleration",
|
||||||
|
"item.extendedae_plus.entity_speed_ticker.tip.energy": "Acceleration Consumes AE Network Energy; Insufficient Network Energy Prevents Acceleration",
|
||||||
|
|
||||||
|
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Information",
|
||||||
|
"config.jade.plugin_extendedae_plus.wt_frequency": "Show Frequency",
|
||||||
|
"config.jade.plugin_extendedae_plus.wt_master_mode": "Show Master/Slave Mode",
|
||||||
|
"config.jade.plugin_extendedae_plus.wt_master_location": "Show Master Node Location",
|
||||||
|
"config.jade.plugin_extendedae_plus.wt_locked": "Show Lock Status",
|
||||||
|
"config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online Status",
|
||||||
|
"extendedae_plus.tooltip.frequency": "Frequency: %d",
|
||||||
|
"extendedae_plus.tooltip.master_mode": "Mode: %s",
|
||||||
|
"extendedae_plus.tooltip.locked": "Lock Status: %s",
|
||||||
"tooltip.extendedae_plus.encode_button_description": "\n§a[Ctrl] §7Upload patterns",
|
"tooltip.extendedae_plus.encode_button_description": "\n§a[Ctrl] §7Upload patterns",
|
||||||
|
|
||||||
|
"screen.extendedae_plus.title": "ExtendedAE Plus Configuration",
|
||||||
"screen.extendedae_plus.title": "ExtendedAE Plus Config",
|
|
||||||
"config.extendedae_plus": "ExtendedAE Plus",
|
"config.extendedae_plus": "ExtendedAE Plus",
|
||||||
"config.extendedae_plus.override_ae2wt_picking": "Override AE2WT Picking",
|
|
||||||
"config.extendedae_plus.independence_uploading_button": "Independence Upload Button",
|
|
||||||
|
|
||||||
|
"extendedae_plus.configuration.common": "Common Configuration",
|
||||||
|
"extendedae_plus.configuration.pageMultiplier": "Pattern Provider Slot Multiplier",
|
||||||
|
"extendedae_plus.configuration.pageMultiplier_with_range": "Pattern Provider Slot Multiplier (1-64)",
|
||||||
|
"extendedae_plus.configuration.override_ae2wt_picking": "Override AE2WT Picking",
|
||||||
|
|
||||||
"extendedae_plus.configuration.general": "General",
|
"extendedae_plus.configuration.client": "Display Configuration",
|
||||||
"extendedae_plus.configuration.pageMultiplier": "Pattern Provider Page Multiplier",
|
"extendedae_plus.configuration.showEncoderPatternPlayer": "Show Pattern Encoder Player",
|
||||||
"extendedae_plus.configuration.pageMultiplier_with_range": "Pattern Provider Page Multiplier (1-64)",
|
|
||||||
"extendedae_plus.configuration.showEncoderPatternPlayer": "Show Encoder Player",
|
|
||||||
"extendedae_plus.configuration.providerRoundRobinEnable": "Enable Provider Round-Robin",
|
|
||||||
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "Pattern Terminal Show Slots by Default",
|
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "Pattern Terminal Show Slots by Default",
|
||||||
|
"extendedae_plus.configuration.independence_uploading_button": "Independence Upload Button",
|
||||||
|
|
||||||
"extendedae_plus.configuration.smartScaling": "Smart Scaling",
|
|
||||||
"extendedae_plus.configuration.smartScalingMaxMultiplier": "Smart Scaling Max Multiplier",
|
|
||||||
"extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Max Multiplier (0 = Unlimited)",
|
|
||||||
|
|
||||||
"extendedae_plus.configuration.wireless": "WirelessTransceiverBlock",
|
"extendedae_plus.configuration.ae": "AE2 Configuration",
|
||||||
"extendedae_plus.configuration.wirelessMaxRange": "Wireless Max Range",
|
"extendedae_plus.configuration.craftingPauseThreshold": "AE Crafting Pause Threshold",
|
||||||
"extendedae_plus.configuration.wirelessMaxRange_with_range": "Wireless Max Range (1-4096)",
|
"extendedae_plus.configuration.smartScaling": "Smart Pattern Scaling",
|
||||||
"extendedae_plus.configuration.wirelessCrossDimEnable": "Allow Wireless Cross-Dimension",
|
"extendedae_plus.configuration.providerRoundRobinEnable": "Enable Pattern Provider Round-Robin",
|
||||||
|
"extendedae_plus.configuration.smartScalingMaxMultiplier": "Smart Scaling Maximum Multiplier",
|
||||||
|
"extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "Smart Scaling Maximum Multiplier (0 for unlimited)",
|
||||||
|
"extendedae_plus.configuration.wireless": "Wireless Connector Configuration",
|
||||||
|
"extendedae_plus.configuration.wirelessMaxRange": "Wireless Maximum Range",
|
||||||
|
"extendedae_plus.configuration.wirelessMaxRange_with_range": "Wireless Maximum Range (1-4096)",
|
||||||
|
"extendedae_plus.configuration.wirelessCrossDimEnable": "Enable Wireless Cross-Dimensional Connection",
|
||||||
|
"extendedae_plus.configuration.entitySpeedTicker": "Entity Speed Ticker",
|
||||||
|
"extendedae_plus.configuration.entityTickerCost": "Entity Ticker Base Energy Cost",
|
||||||
|
"extendedae_plus.configuration.entityTickerBlackList": "Entity Ticker Blacklist",
|
||||||
|
"extendedae_plus.configuration.entityTickerMultipliers": "Entity Ticker Extra Cost Multipliers",
|
||||||
|
"extendedae_plus.configuration.prioritizeDiskEnergy": "Prioritize FE energy from disk (requires Applied Flux)",
|
||||||
|
|
||||||
"extendedae_plus.configuration.state_on": "On",
|
"extendedae_plus.configuration.state_on": "On",
|
||||||
"extendedae_plus.configuration.state_off": "Off"
|
"extendedae_plus.configuration.state_off": "Off"
|
||||||
}
|
}
|
||||||
|
|
@ -3,9 +3,46 @@
|
||||||
"gui.expatternprovider.hide_slots": "隐藏槽位",
|
"gui.expatternprovider.hide_slots": "隐藏槽位",
|
||||||
"gui.expatternprovider.show_slots": "显示槽位",
|
"gui.expatternprovider.show_slots": "显示槽位",
|
||||||
"gui.expatternprovider.clear_selection": "取消选择",
|
"gui.expatternprovider.clear_selection": "取消选择",
|
||||||
"block.extendedae_plus.wireless_transceiver": "无线收发器",
|
"gui.extendedae_plus.global.toggle_blocking": "切换阻挡模式",
|
||||||
"item.extendedae_plus.wireless_transceiver": "无线收发器",
|
"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",
|
"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.assembler_matrix_upload_core": "装配矩阵上传核心",
|
||||||
|
"item.extendedae_plus.channel_card": "频道卡",
|
||||||
|
"item.extendedae_plus.channel_card.channel": "频道: %d",
|
||||||
|
"item.extendedae_plus.channel_card.channel.unset": "频道: 未设置",
|
||||||
|
"item.extendedae_plus.channel_card.set": "频道已设置为: %d",
|
||||||
|
"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 倍",
|
||||||
|
"tooltip.extendedae_plus.encode_button_description": "\n§a[Ctrl] §7上传样板",
|
||||||
|
|
||||||
|
"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": "样板供应器状态控制器",
|
||||||
|
"block.extendedae_plus.assembler_matrix_upload_core": "装配矩阵上传核心",
|
||||||
|
|
||||||
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
|
"extendedae_plus.upload_to_matrix": "上传到装配矩阵",
|
||||||
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
|
"extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵",
|
||||||
"extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
|
"extendedae_plus.upload_to_matrix.fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略",
|
||||||
|
|
@ -17,21 +54,17 @@
|
||||||
"extendedae_plus.button.choose_provider":"上传样板",
|
"extendedae_plus.button.choose_provider":"上传样板",
|
||||||
"extendedae_plus.pattern.hovertext.player": "由 %s 编码",
|
"extendedae_plus.pattern.hovertext.player": "由 %s 编码",
|
||||||
|
|
||||||
"block.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
"item.extendedae_plus.entity_speed_ticker": "实体加速器",
|
||||||
"block.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
"screen.extendedae_plus.entity_speed_ticker.enable": "§c§l机器已被禁用",
|
||||||
"block.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
"screen.extendedae_plus.entity_speed_ticker.energy": "能耗: %s FE/t",
|
||||||
"block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
"screen.extendedae_plus.entity_speed_ticker.power_ratio": "功耗比例: %s",
|
||||||
"block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
"screen.extendedae_plus.entity_speed_ticker.speed": "当前加速倍率: %d",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.multiplier": "额外消耗倍率: %s",
|
||||||
|
"screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§l网络能量不足",
|
||||||
|
|
||||||
"item.extendedae_plus.4x_crafting_accelerator": "4x并行处理单元",
|
"item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速",
|
||||||
"item.extendedae_plus.16x_crafting_accelerator": "16x并行处理单元",
|
"item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",
|
||||||
"item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元",
|
"item.extendedae_plus.entity_speed_ticker.tip.energy": "加速将消耗 AE 网络能量,网络能量不足时无法加速",
|
||||||
"item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元",
|
|
||||||
"item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元",
|
|
||||||
"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.encode_button_description": "\n§a[Ctrl] §7上传样板",
|
|
||||||
|
|
||||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
|
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
|
||||||
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",
|
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",
|
||||||
|
|
@ -45,33 +78,33 @@
|
||||||
|
|
||||||
"screen.extendedae_plus.title": "ExtendedAE Plus 配置",
|
"screen.extendedae_plus.title": "ExtendedAE Plus 配置",
|
||||||
"config.extendedae_plus": "ExtendedAE Plus",
|
"config.extendedae_plus": "ExtendedAE Plus",
|
||||||
"config.extendedae_plus.override_ae2wt_picking": "覆盖AE2WT选取",
|
|
||||||
"config.extendedae_plus.independence_uploading_button": "独立上传按钮",
|
|
||||||
|
|
||||||
"extendedae_plus.configuration.general": "通用",
|
"extendedae_plus.configuration.common": "通用配置",
|
||||||
"extendedae_plus.configuration.pageMultiplier": "扩展样板供应器槽位倍率",
|
"extendedae_plus.configuration.pageMultiplier": "扩展样板供应器槽位倍率",
|
||||||
"extendedae_plus.configuration.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
|
"extendedae_plus.configuration.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
|
||||||
|
"extendedae_plus.configuration.override_ae2wt_picking": "覆盖AE2WT选取",
|
||||||
|
|
||||||
|
"extendedae_plus.configuration.client": "显示配置",
|
||||||
"extendedae_plus.configuration.showEncoderPatternPlayer": "显示样板编码玩家",
|
"extendedae_plus.configuration.showEncoderPatternPlayer": "显示样板编码玩家",
|
||||||
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
|
"extendedae_plus.configuration.patternTerminalShowSlotsDefault": "样板终端默认显示槽位",
|
||||||
|
"extendedae_plus.configuration.independence_uploading_button": "独立上传按钮",
|
||||||
|
|
||||||
|
"extendedae_plus.configuration.ae": "AE2配置",
|
||||||
|
"extendedae_plus.configuration.craftingPauseThreshold": "AE合成计算暂停检查阈值",
|
||||||
"extendedae_plus.configuration.smartScaling": "智能样板倍增",
|
"extendedae_plus.configuration.smartScaling": "智能样板倍增",
|
||||||
"extendedae_plus.configuration.providerRoundRobinEnable": "启用样板供应器轮询分配",
|
"extendedae_plus.configuration.providerRoundRobinEnable": "启用样板供应器轮询分配",
|
||||||
"extendedae_plus.configuration.smartScalingMaxMultiplier": "智能倍增最大倍数",
|
"extendedae_plus.configuration.smartScalingMaxMultiplier": "智能倍增最大倍数",
|
||||||
"extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "智能倍增最大倍数 (0为不限制)",
|
"extendedae_plus.configuration.smartScalingMaxMultiplier_with_range": "智能倍增最大倍数 (0为不限制)",
|
||||||
|
|
||||||
"extendedae_plus.configuration.wireless": "无线连接器配置",
|
"extendedae_plus.configuration.wireless": "无线连接器配置",
|
||||||
"extendedae_plus.configuration.wirelessMaxRange": "无线最大距离",
|
"extendedae_plus.configuration.wirelessMaxRange": "无线最大距离",
|
||||||
"extendedae_plus.configuration.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
|
"extendedae_plus.configuration.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
|
||||||
"extendedae_plus.configuration.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
|
"extendedae_plus.configuration.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
|
||||||
|
"extendedae_plus.configuration.entitySpeedTicker": "实体加速器",
|
||||||
|
"extendedae_plus.configuration.entityTickerCost": "实体加速器能量消耗基础值",
|
||||||
|
"extendedae_plus.configuration.entityTickerBlackList": "实体加速器黑名单",
|
||||||
|
"extendedae_plus.configuration.entityTickerMultipliers": "实体加速器额外消耗倍率",
|
||||||
|
"extendedae_plus.configuration.prioritizeDiskEnergy": "优先从磁盘提取FE能量(仅当Applied Flux模组存在时生效)",
|
||||||
|
|
||||||
"extendedae_plus.configuration.state_on": "开",
|
"extendedae_plus.configuration.state_on": "开",
|
||||||
"extendedae_plus.configuration.state_off": "关",
|
"extendedae_plus.configuration.state_off": "关"
|
||||||
"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": "全部关闭"
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_all",
|
||||||
|
"textures": {
|
||||||
|
"all": "extendedae_plus:block/assembler_matrix_upload_core"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"parent": "extendedae_plus:block/assembler_matrix_upload_core"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"parent": "item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "extendedae_plus:item/channel_card"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user