Merge branch 'master' into pattern-encode-player
This commit is contained in:
commit
c8a0699168
|
|
@ -102,8 +102,9 @@ dependencies {
|
|||
//jec
|
||||
modCompileOnly "curse.maven:just-enough-characters-250702:6680042"
|
||||
|
||||
|
||||
//mae2
|
||||
modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6342203"
|
||||
// modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6342203"
|
||||
modCompileOnly "curse.maven:modern-ae2-additions-1028068:6342203"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G
|
|||
loom.platform = forge
|
||||
|
||||
# Mod properties
|
||||
mod_version = 1.3.3
|
||||
mod_version = 1.4.0
|
||||
maven_group = com.extendedae_plus
|
||||
archives_name = extendedae_plus
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ glodium_version=5006780
|
|||
ae2_version=15.4.2
|
||||
guideme_version=20.1.7
|
||||
wireless_terminals_version=5162352
|
||||
jei_version=15.0.0.12
|
||||
jei_version=15.19.5.99
|
||||
applied_flux_version=5329825
|
||||
mega_cells_version=5320730
|
||||
jade_version=4768593
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import com.extendedae_plus.init.ModBlockEntities;
|
|||
import com.extendedae_plus.init.ModBlocks;
|
||||
import com.extendedae_plus.init.ModCreativeTabs;
|
||||
import com.extendedae_plus.init.ModItems;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.menu.locator.CuriosItemLocator;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import net.minecraftforge.client.ConfigScreenHandler;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
|
|
@ -20,6 +22,7 @@ import net.minecraft.resources.ResourceLocation;
|
|||
import com.extendedae_plus.client.ClientProxy;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
|
||||
/**
|
||||
* ExtendedAE Plus 主mod类
|
||||
|
|
@ -29,17 +32,14 @@ public class ExtendedAEPlus {
|
|||
|
||||
public static final String MODID = "extendedae_plus";
|
||||
|
||||
// 在类加载时(尽可能早)在客户端注册内置模型,避免首次资源加载时错过。
|
||||
static {
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
|
||||
System.out.println("[ExtendedAE_Plus] Static init: register built-in models");
|
||||
ClientProxy.init();
|
||||
});
|
||||
}
|
||||
// 注意:避免在静态初始化阶段访问注册对象,相关客户端注册改在 FMLClientSetupEvent 中执行。
|
||||
|
||||
public ExtendedAEPlus() {
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
||||
// 在客户端尽早注册内置模型,保证首次资源加载前映射已建立(仿照 AE2 的 AppEngClient 构造期注册)
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientProxy::init);
|
||||
|
||||
// 注册mod初始化事件
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
|
||||
|
|
@ -48,6 +48,7 @@ public class ExtendedAEPlus {
|
|||
ModBlockEntities.BLOCK_ENTITY_TYPES.register(modEventBus);
|
||||
ModItems.ITEMS.register(modEventBus);
|
||||
ModCreativeTabs.TABS.register(modEventBus);
|
||||
ModMenuTypes.MENUS.register(modEventBus);
|
||||
|
||||
// 注册到Forge事件总线
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
|
|
@ -55,8 +56,8 @@ public class ExtendedAEPlus {
|
|||
// 注册通用配置
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
|
||||
|
||||
// 构造期在客户端再确保一次注册(幂等)
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientProxy::init);
|
||||
// 客户端侧延迟注册:在 FMLClientSetupEvent 阶段执行(包含 MenuScreens 绑定等)
|
||||
modEventBus.addListener((FMLClientSetupEvent e) -> ClientProxy.onClientSetup(e));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.extendedae_plus.api;
|
||||
|
||||
public interface PatternProviderMenuDoublingSync {
|
||||
boolean eap$getSmartDoublingSynced();
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.extendedae_plus.api;
|
||||
|
||||
public interface SmartDoublingAwarePattern {
|
||||
boolean eap$allowScaling();
|
||||
void eap$setAllowScaling(boolean allow);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.extendedae_plus.api;
|
||||
|
||||
public interface SmartDoublingHolder {
|
||||
boolean eap$getSmartDoubling();
|
||||
void eap$setSmartDoubling(boolean value);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ModelEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
|
@ -8,13 +9,19 @@ import net.minecraftforge.fml.common.Mod;
|
|||
/**
|
||||
* 确保在模型烘焙/资源重载期间也会注册内置模型,避免在刷新资源后丢失内置模型映射。
|
||||
*/
|
||||
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
@Mod.EventBusSubscriber(modid = ExtendedAEPlus.MODID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public final class ClientModelEvents {
|
||||
private ClientModelEvents() {}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterAdditional(ModelEvent.RegisterAdditional event) {
|
||||
// 在每次模型重载开始时确保内置模型已注册
|
||||
// 先显式登记这些模型ID,使其在首次加载阶段被请求,从而触发我们的内置模型拦截
|
||||
event.register(ExtendedAEPlus.id("block/crafting/4x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/16x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/64x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/256x_accelerator_formed_v2"));
|
||||
event.register(ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"));
|
||||
ClientProxy.init();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ package com.extendedae_plus.client;
|
|||
import appeng.client.render.crafting.CraftingCubeModel;
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.client.render.crafting.EPlusCraftingCubeModelProvider;
|
||||
import com.extendedae_plus.client.screen.GlobalProviderModesScreen;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.content.crafting.EPlusCraftingUnitType;
|
||||
import com.extendedae_plus.hooks.BuiltInModelHooks;
|
||||
import net.minecraftforge.client.ConfigScreenHandler;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
|
||||
/**
|
||||
* 客户端模型注册,将 formed 模型注册为内置模型。
|
||||
|
|
@ -38,4 +44,31 @@ public final class ClientProxy {
|
|||
ExtendedAEPlus.id("block/crafting/1024x_accelerator_formed_v2"),
|
||||
new CraftingCubeModel(new EPlusCraftingCubeModelProvider(EPlusCraftingUnitType.ACCELERATOR_1024x)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端设置阶段:延迟执行需要访问注册对象的客户端注册。
|
||||
*/
|
||||
public static void onClientSetup(final FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> {
|
||||
// 确保在首次资源加载前完成内置模型注册(REGISTERED 保护避免重复)
|
||||
init();
|
||||
// 仅在客户端设置阶段执行与 UI 相关的一次性绑定
|
||||
registerConfigScreen();
|
||||
// 菜单 -> 屏幕 绑定
|
||||
MenuScreens.register(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), GlobalProviderModesScreen::new);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅客户端:在 Mods 菜单注册配置界面入口。
|
||||
* 将对 Screen 的引用限制在客户端侧,避免服务端类加载。
|
||||
*/
|
||||
public static void registerConfigScreen() {
|
||||
// 将 ModConfigScreen 的引用放在此处,确保仅在 Dist.CLIENT 下解析该类
|
||||
ModLoadingContext.get().registerExtensionPoint(
|
||||
ConfigScreenHandler.ConfigScreenFactory.class,
|
||||
() -> new ConfigScreenHandler.ConfigScreenFactory(
|
||||
(mc, parent) -> new com.extendedae_plus.client.ModConfigScreen(parent))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
134
src/main/java/com/extendedae_plus/client/ModConfigScreen.java
Normal file
134
src/main/java/com/extendedae_plus/client/ModConfigScreen.java
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
package com.extendedae_plus.client;
|
||||
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.CycleButton;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class ModConfigScreen extends Screen {
|
||||
private final Screen parent;
|
||||
|
||||
// 输入控件
|
||||
private EditBox pageMultiplierBox;
|
||||
private EditBox wirelessMaxRangeBox;
|
||||
private CycleButton<Boolean> crossDimToggle;
|
||||
private CycleButton<Boolean> providerRoundRobinToggle;
|
||||
|
||||
public ModConfigScreen(Screen parent) {
|
||||
super(Component.translatable("screen.extendedae_plus.title"));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
int centerX = this.width / 2;
|
||||
int y = this.height / 6 + 24; // 起始高度,整体更上方
|
||||
int row = 0;
|
||||
int rowHeight = 26;
|
||||
int boxWidth = 150;
|
||||
// 左右两列:左侧标签起点,右侧输入控件起点
|
||||
int leftX = centerX - 170;
|
||||
int rightX = centerX + 20;
|
||||
|
||||
// pageMultiplier: Int 1-64
|
||||
pageMultiplierBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.pageMultiplier"));
|
||||
pageMultiplierBox.setValue(String.valueOf(ModConfigs.PAGE_MULTIPLIER.get()));
|
||||
pageMultiplierBox.setFilter(s -> s.matches("\\d*") && parseIntOrDefault(s, 1) >= 1 && parseIntOrDefault(s, 64) <= 64);
|
||||
this.addRenderableWidget(pageMultiplierBox);
|
||||
row++;
|
||||
|
||||
// wirelessMaxRange: Double 1-4096
|
||||
wirelessMaxRangeBox = new EditBox(this.font, rightX, y + row * rowHeight, boxWidth, 20, Component.translatable("config.extendedae_plus.wirelessMaxRange"));
|
||||
wirelessMaxRangeBox.setValue(String.valueOf(ModConfigs.WIRELESS_MAX_RANGE.get()));
|
||||
wirelessMaxRangeBox.setFilter(s -> s.isEmpty() || s.matches("\\d*(\\.\\d*)?"));
|
||||
this.addRenderableWidget(wirelessMaxRangeBox);
|
||||
row++;
|
||||
|
||||
// cross dim toggle
|
||||
crossDimToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.WIRELESS_CROSS_DIM_ENABLE.get()));
|
||||
row++;
|
||||
|
||||
// provider round-robin toggle (smart doubling)
|
||||
providerRoundRobinToggle = this.addRenderableWidget(createToggle(rightX, y + row * rowHeight, boxWidth, 20, ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get()));
|
||||
row++;
|
||||
|
||||
// 按钮:保存、返回
|
||||
int btnW = 100;
|
||||
int gap = 8;
|
||||
int buttonsY = y + row * rowHeight + 18;
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("gui.done"), b -> saveAndClose())
|
||||
.pos(centerX - btnW - gap/2, buttonsY)
|
||||
.size(btnW, 20)
|
||||
.build());
|
||||
this.addRenderableWidget(Button.builder(Component.translatable("gui.cancel"), b -> onClose())
|
||||
.pos(centerX + gap/2, buttonsY)
|
||||
.size(btnW, 20)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void saveAndClose() {
|
||||
// 读取与校验
|
||||
int pageMul = clamp(parseIntOrDefault(pageMultiplierBox.getValue(), ModConfigs.PAGE_MULTIPLIER.get()), 1, 64);
|
||||
double maxRange = clamp(parseDoubleOrDefault(wirelessMaxRangeBox.getValue(), ModConfigs.WIRELESS_MAX_RANGE.get()), 1.0, 4096.0);
|
||||
boolean crossDim = crossDimToggle.getValue();
|
||||
boolean providerRoundRobin = providerRoundRobinToggle.getValue();
|
||||
|
||||
// 应用到 Forge 配置值
|
||||
ModConfigs.PAGE_MULTIPLIER.set(pageMul);
|
||||
ModConfigs.WIRELESS_MAX_RANGE.set(maxRange);
|
||||
ModConfigs.WIRELESS_CROSS_DIM_ENABLE.set(crossDim);
|
||||
ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.set(providerRoundRobin);
|
||||
|
||||
// Forge 会在合适的时机写回到配置文件;部分改动可能需要重启游戏或世界才完全生效
|
||||
onClose();
|
||||
}
|
||||
|
||||
// Helper to create a boolean on/off CycleButton which shows localized on/off text
|
||||
private CycleButton<Boolean> createToggle(int x, int y, int width, int height, boolean initial) {
|
||||
CycleButton<Boolean> btn = CycleButton.onOffBuilder(initial)
|
||||
.create(x, y, width, height, Component.empty(), (b, v) -> b.setMessage(Component.translatable(v ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off")));
|
||||
btn.setMessage(Component.translatable(initial ? "config.extendedae_plus.state_on" : "config.extendedae_plus.state_off"));
|
||||
return btn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
Minecraft.getInstance().setScreen(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics g, int mouseX, int mouseY, float partialTick) {
|
||||
this.renderBackground(g);
|
||||
super.render(g, mouseX, mouseY, partialTick);
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int y = this.height / 6 + 24;
|
||||
int rowHeight = 26;
|
||||
int labelColor = 0xFFFFFF;
|
||||
int leftX = centerX - 170; // 标签左列位置
|
||||
|
||||
// 标题
|
||||
g.drawCenteredString(this.font, this.title, centerX, y - 28, 0xFFFFFF);
|
||||
|
||||
// 每行标签
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.pageMultiplier_with_range"), leftX, y + 0 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessMaxRange_with_range"), leftX, y + 1 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.wirelessCrossDimEnable"), leftX, y + 2 * rowHeight + 6, labelColor, false);
|
||||
g.drawString(this.font, Component.translatable("config.extendedae_plus.providerRoundRobinEnable"), leftX, y + 3 * rowHeight + 6, labelColor, false);
|
||||
}
|
||||
|
||||
private static int parseIntOrDefault(String s, int def) {
|
||||
try { return Integer.parseInt(s); } catch (Exception e) { return def; }
|
||||
}
|
||||
|
||||
private static double parseDoubleOrDefault(String s, double def) {
|
||||
try { return Double.parseDouble(s); } catch (Exception e) { return def; }
|
||||
}
|
||||
|
||||
private static int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }
|
||||
private static double clamp(double v, double min, double max) { return Math.max(min, Math.min(max, v)); }
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import java.util.List;
|
|||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 参照 MAE2 的 DynamicCraftingCubeModelProvider,实现形成态光照模型。
|
||||
* 形成态光照模型。
|
||||
*/
|
||||
public class EPlusCraftingCubeModelProvider
|
||||
extends AbstractCraftingUnitModelProvider<EPlusCraftingUnitType> {
|
||||
|
|
@ -29,13 +29,13 @@ public class EPlusCraftingCubeModelProvider
|
|||
public static final ChunkRenderTypeSet CUTOUT = ChunkRenderTypeSet.of(RenderType.cutout());
|
||||
private static final List<Material> MATERIALS = new ArrayList<>();
|
||||
|
||||
// 与 MAE2 一致:将环形边框与基础发光底图放在本模组命名空间
|
||||
//将环形边框与基础发光底图放在本模组命名空间
|
||||
protected static final Material RING_CORNER = texture(ExtendedAEPlus.MODID, "ring_corner");
|
||||
protected static final Material RING_SIDE_HOR = texture(ExtendedAEPlus.MODID, "ring_side_hor");
|
||||
protected static final Material RING_SIDE_VER = texture(ExtendedAEPlus.MODID, "ring_side_ver");
|
||||
protected static final Material LIGHT_BASE = texture(ExtendedAEPlus.MODID, "light_base");
|
||||
|
||||
// 我们自己的亮面贴图(formed 时使用)
|
||||
// 亮面贴图(formed 时使用)
|
||||
protected static final Material ACCELERATOR_4X_LIGHT = texture(ExtendedAEPlus.MODID,
|
||||
"4x_accelerator_light");
|
||||
protected static final Material ACCELERATOR_16X_LIGHT = texture(ExtendedAEPlus.MODID,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
package com.extendedae_plus.client.screen;
|
||||
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import com.extendedae_plus.network.GlobalToggleProviderModesC2SPacket;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
|
||||
public class GlobalProviderModesScreen extends AbstractContainerScreen<NetworkPatternControllerMenu> {
|
||||
private static final Component CUSTOM_TITLE = Component.literal("样板供应器状态控制器");
|
||||
public GlobalProviderModesScreen(NetworkPatternControllerMenu menu, Inventory inv, Component title) {
|
||||
super(menu, inv, title);
|
||||
this.imageWidth = 240;
|
||||
this.imageHeight = 140;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
int w = 70; // 按钮宽
|
||||
int h = 20; // 按钮高
|
||||
int s = 8; // 按钮间距
|
||||
int y = this.topPos + 28; // 第一行 Y
|
||||
// 计算三列按钮的左侧起点,使其在面板内水平居中
|
||||
int totalW3 = w * 3 + s * 2;
|
||||
int x = this.leftPos + (this.imageWidth - totalW3) / 2;
|
||||
|
||||
// 行1:三个单项切换
|
||||
addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_blocking"), b ->
|
||||
ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket(
|
||||
GlobalToggleProviderModesC2SPacket.Op.TOGGLE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
this.menu.getBlockEntityPos()
|
||||
))).bounds(x, y, w, h).build());
|
||||
|
||||
addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_adv_blocking"), b ->
|
||||
ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket(
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
GlobalToggleProviderModesC2SPacket.Op.TOGGLE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
this.menu.getBlockEntityPos()
|
||||
))).bounds(x + w + s, y, w, h).build());
|
||||
|
||||
addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.toggle_smart_doubling"), b ->
|
||||
ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket(
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
GlobalToggleProviderModesC2SPacket.Op.NOOP,
|
||||
GlobalToggleProviderModesC2SPacket.Op.TOGGLE,
|
||||
this.menu.getBlockEntityPos()
|
||||
))).bounds(x + (w + s) * 2, y, w, h).build());
|
||||
|
||||
// 行2:一键全开/全关
|
||||
int y2 = y + h + 12;
|
||||
// 第二行:两列按钮,总宽并居中
|
||||
int totalW2 = w * 2 + s;
|
||||
int x2 = this.leftPos + (this.imageWidth - totalW2) / 2;
|
||||
addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.all_on"), b ->
|
||||
ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket(
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_TRUE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_TRUE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_TRUE,
|
||||
this.menu.getBlockEntityPos()
|
||||
))).bounds(x2, y2, w, h).build());
|
||||
|
||||
addRenderableWidget(Button.builder(Component.translatable("gui.extendedae_plus.global.all_off"), b ->
|
||||
ModNetwork.CHANNEL.sendToServer(new GlobalToggleProviderModesC2SPacket(
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_FALSE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_FALSE,
|
||||
GlobalToggleProviderModesC2SPacket.Op.SET_FALSE,
|
||||
this.menu.getBlockEntityPos()
|
||||
))).bounds(x2 + w + s, y2, w, h).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderBg(net.minecraft.client.gui.GuiGraphics gfx, float partialTicks, int mouseX, int mouseY) {
|
||||
// 半透明全屏遮罩,避免底层 HUD(准星/物品栏文字)透出
|
||||
gfx.fill(0, 0, this.width, this.height, 0xC0000000);
|
||||
|
||||
// 在按钮区域绘制一个半透明面板,提升可读性
|
||||
int pad = 6;
|
||||
int panelLeft = this.leftPos - pad;
|
||||
int panelTop = this.topPos - pad;
|
||||
int panelRight = this.leftPos + this.imageWidth + pad;
|
||||
int panelBottom = this.topPos + this.imageHeight + pad;
|
||||
gfx.fill(panelLeft, panelTop, panelRight, panelBottom, 0xA01E1E1E);
|
||||
// 边框
|
||||
gfx.fill(panelLeft, panelTop, panelRight, panelTop + 1, 0x80FFFFFF);
|
||||
gfx.fill(panelLeft, panelBottom - 1, panelRight, panelBottom, 0x80000000);
|
||||
gfx.fill(panelLeft, panelTop, panelLeft + 1, panelBottom, 0x80FFFFFF);
|
||||
gfx.fill(panelRight - 1, panelTop, panelRight, panelBottom, 0x80000000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(net.minecraft.client.gui.GuiGraphics gfx, int mouseX, int mouseY, float partialTicks) {
|
||||
this.renderBackground(gfx);
|
||||
super.render(gfx, mouseX, mouseY, partialTicks);
|
||||
gfx.drawString(this.font, CUSTOM_TITLE, this.leftPos + 10, this.topPos + 8, 0xFFFFFF, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderLabels(net.minecraft.client.gui.GuiGraphics gfx, int mouseX, int mouseY) {
|
||||
// 不绘制默认的玩家物品栏标题(例如“物品栏”),避免与自定义面板重叠
|
||||
// 标题已在 render() 中手动绘制
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ public final class ModConfigs {
|
|||
public static final ForgeConfigSpec.DoubleValue WIRELESS_MAX_RANGE;
|
||||
public static final ForgeConfigSpec.BooleanValue WIRELESS_CROSS_DIM_ENABLE;
|
||||
public static final ForgeConfigSpec.BooleanValue SHOW_ENCOD_PATTERN_PLAYER;
|
||||
public static final ForgeConfigSpec.BooleanValue PROVIDER_ROUND_ROBIN_ENABLE;
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
|
|
@ -41,6 +42,14 @@ public final class ModConfigs {
|
|||
)
|
||||
.define("showEncoderPatternPlayer", true);
|
||||
|
||||
// 智能倍增后,是否在样板供应器间轮询分配请求量(开启:按 provider 均分;关闭:不拆分)
|
||||
PROVIDER_ROUND_ROBIN_ENABLE = builder
|
||||
.comment(
|
||||
"智能倍增时是否对样板供应器轮询分配",
|
||||
"仅多个供应器有相同样板时生效,开启后请求会均分到所有可用供应器,关闭则全部分配给单一供应器",
|
||||
"注意:所有相关供应器需开启智能倍增,否则可能失效",
|
||||
"默认: true")
|
||||
.define("providerRoundRobinEnable", true);
|
||||
builder.pop();
|
||||
COMMON_SPEC = builder.build();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
package com.extendedae_plus.content;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.api.stacks.KeyCounter;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 缩放后的处理样板,结构完全模拟 AEProcessingPattern。
|
||||
* 保持 sparse/condensed/inputs 的一致性,同时保存原始样板。
|
||||
*/
|
||||
public final class ScaledProcessingPattern implements IPatternDetails {
|
||||
|
||||
private final AEProcessingPattern original; // 原始样板引用
|
||||
private final AEItemKey definition; // 样板物品
|
||||
private final GenericStack[] sparseInputs; // 缩放后的稀疏输入
|
||||
private final GenericStack[] sparseOutputs; // 缩放后的稀疏输出
|
||||
private final IInput[] inputs; // 缩放后的压缩输入
|
||||
private final GenericStack[] condensedOutputs; // 缩放后的压缩输出
|
||||
|
||||
public ScaledProcessingPattern(
|
||||
AEProcessingPattern original,
|
||||
AEItemKey definition,
|
||||
GenericStack[] sparseInputs,
|
||||
GenericStack[] sparseOutputs,
|
||||
IInput[] inputs,
|
||||
GenericStack[] condensedOutputs
|
||||
) {
|
||||
this.original = Objects.requireNonNull(original);
|
||||
this.definition = Objects.requireNonNull(definition);
|
||||
this.sparseInputs = Objects.requireNonNull(sparseInputs);
|
||||
this.sparseOutputs = Objects.requireNonNull(sparseOutputs);
|
||||
this.inputs = Objects.requireNonNull(inputs);
|
||||
this.condensedOutputs = Objects.requireNonNull(condensedOutputs);
|
||||
}
|
||||
|
||||
/* -------------------- API 实现 -------------------- */
|
||||
|
||||
public AEProcessingPattern getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AEItemKey getDefinition() {
|
||||
return definition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IInput[] getInputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericStack[] getOutputs() {
|
||||
return condensedOutputs;
|
||||
}
|
||||
|
||||
public GenericStack[] getSparseInputs() {
|
||||
return sparseInputs;
|
||||
}
|
||||
|
||||
public GenericStack[] getSparseOutputs() {
|
||||
return sparseOutputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GenericStack getPrimaryOutput() {
|
||||
if (condensedOutputs.length > 0) return condensedOutputs[0];
|
||||
return original.getPrimaryOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPushInputsToExternalInventory() {
|
||||
return original.supportsPushInputsToExternalInventory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) {
|
||||
// 保持和 AEProcessingPattern 一致,用 sparseInputs 驱动
|
||||
if (sparseInputs.length == inputs.length) {
|
||||
IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink);
|
||||
} else {
|
||||
KeyCounter allInputs = new KeyCounter();
|
||||
for (KeyCounter counter : inputHolder) {
|
||||
allInputs.addAll(counter);
|
||||
}
|
||||
for (GenericStack sparseInput : sparseInputs) {
|
||||
if (sparseInput != null) {
|
||||
AEKey key = sparseInput.what();
|
||||
long amount = sparseInput.amount();
|
||||
long available = allInputs.get(key);
|
||||
if (available < amount) {
|
||||
throw new RuntimeException("Expected at least %d of %s when pushing scaled pattern, but only %d available"
|
||||
.formatted(amount, key, available));
|
||||
}
|
||||
inputSink.pushInput(key, amount);
|
||||
allInputs.remove(key, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------- 缩放输入代理 -------------------- */
|
||||
|
||||
public static final class Input implements IPatternDetails.IInput {
|
||||
private final GenericStack[] template;
|
||||
private final long multiplier;
|
||||
|
||||
public Input(GenericStack[] template, long multiplier) {
|
||||
this.template = template;
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
public GenericStack[] getPossibleInputs() {
|
||||
return this.template;
|
||||
}
|
||||
|
||||
public long getMultiplier() {
|
||||
return this.multiplier;
|
||||
}
|
||||
|
||||
public boolean isValid(AEKey input, Level level) {
|
||||
return input.matches(this.template[0]);
|
||||
}
|
||||
|
||||
public @Nullable AEKey getRemainingKey(AEKey template) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.extendedae_plus.content.controller;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
public class NetworkPatternControllerBlock extends Block implements EntityBlock {
|
||||
|
||||
public NetworkPatternControllerBlock(Properties props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new NetworkPatternControllerBlockEntity(pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
|
||||
if (!level.isClientSide) {
|
||||
BlockEntity be = level.getBlockEntity(pos);
|
||||
if (be instanceof MenuProvider provider && player instanceof ServerPlayer sp) {
|
||||
NetworkHooks.openScreen(sp, provider, pos);
|
||||
}
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.extendedae_plus.content.controller;
|
||||
|
||||
import appeng.api.networking.GridHelper;
|
||||
import appeng.api.networking.GridFlags;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.IGridNodeListener;
|
||||
import appeng.api.networking.IInWorldGridNodeHost;
|
||||
import appeng.api.networking.IManagedGridNode;
|
||||
import com.extendedae_plus.init.ModBlockEntities;
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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.state.BlockState;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class NetworkPatternControllerBlockEntity extends BlockEntity implements IInWorldGridNodeHost, MenuProvider {
|
||||
|
||||
private final IManagedGridNode managedNode;
|
||||
|
||||
public NetworkPatternControllerBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.NETWORK_PATTERN_CONTROLLER_BE.get(), pos, state);
|
||||
this.managedNode = GridHelper.createManagedNode(this, NodeListener.INSTANCE);
|
||||
this.managedNode.setIdlePowerUsage(1.0);
|
||||
this.managedNode.setInWorldNode(true);
|
||||
this.managedNode.setFlags(GridFlags.REQUIRE_CHANNEL);
|
||||
this.managedNode.setTagName("network_pattern_controller");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IGridNode getGridNode(@Nullable Direction dir) {
|
||||
return managedNode == null ? null : managedNode.getNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
super.onLoad();
|
||||
if (this.level != null && !this.level.isClientSide) {
|
||||
GridHelper.onFirstTick(this, be -> be.managedNode.create(be.getLevel(), be.getBlockPos()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
this.managedNode.saveToNBT(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
super.load(tag);
|
||||
this.managedNode.loadFromNBT(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkUnloaded() {
|
||||
super.onChunkUnloaded();
|
||||
this.managedNode.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoved() {
|
||||
super.setRemoved();
|
||||
this.managedNode.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getDisplayName() {
|
||||
return Component.translatable("block.extendedae_plus.network_pattern_controller");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int id, Inventory inv, Player player) {
|
||||
return new NetworkPatternControllerMenu(id, inv, this.worldPosition);
|
||||
}
|
||||
|
||||
enum NodeListener implements IGridNodeListener<NetworkPatternControllerBlockEntity> {
|
||||
INSTANCE;
|
||||
@Override
|
||||
public void onSaveChanges(NetworkPatternControllerBlockEntity host, IGridNode node) {
|
||||
host.setChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.extendedae_plus.init;
|
|||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
|
||||
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
|
@ -17,4 +18,9 @@ public final class ModBlockEntities {
|
|||
BLOCK_ENTITY_TYPES.register("wireless_transceiver",
|
||||
() -> BlockEntityType.Builder.of(WirelessTransceiverBlockEntity::new,
|
||||
ModBlocks.WIRELESS_TRANSCEIVER.get()).build(null));
|
||||
|
||||
public static final RegistryObject<BlockEntityType<NetworkPatternControllerBlockEntity>> NETWORK_PATTERN_CONTROLLER_BE =
|
||||
BLOCK_ENTITY_TYPES.register("network_pattern_controller",
|
||||
() -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new,
|
||||
ModBlocks.NETWORK_PATTERN_CONTROLLER.get()).build(null));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,17 @@ public final class ModBlocks {
|
|||
)
|
||||
);
|
||||
|
||||
// AE2 网络模式控制器方块
|
||||
public static final RegistryObject<Block> NETWORK_PATTERN_CONTROLLER = BLOCKS.register(
|
||||
"network_pattern_controller",
|
||||
() -> new com.extendedae_plus.content.controller.NetworkPatternControllerBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.METAL)
|
||||
.strength(1.5F, 6.0F)
|
||||
.requiresCorrectToolForDrops()
|
||||
)
|
||||
);
|
||||
|
||||
// Crafting Accelerators (reuse MAE2 textures/models)
|
||||
public static final RegistryObject<CraftingUnitBlock> ACCELERATOR_4x = BLOCKS.register(
|
||||
"4x_crafting_accelerator",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public final class ModCreativeTabs {
|
|||
.displayItems((params, output) -> {
|
||||
// 将本模组物品加入创造物品栏
|
||||
output.accept(ModItems.WIRELESS_TRANSCEIVER.get());
|
||||
output.accept(ModItems.NETWORK_PATTERN_CONTROLLER.get());
|
||||
output.accept(ModItems.ACCELERATOR_4x.get());
|
||||
output.accept(ModItems.ACCELERATOR_16x.get());
|
||||
output.accept(ModItems.ACCELERATOR_64x.get());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ public final class ModItems {
|
|||
() -> new BlockItem(ModBlocks.WIRELESS_TRANSCEIVER.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
public static final RegistryObject<Item> NETWORK_PATTERN_CONTROLLER = ITEMS.register(
|
||||
"network_pattern_controller",
|
||||
() -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties())
|
||||
);
|
||||
|
||||
// Crafting Accelerators
|
||||
public static final RegistryObject<Item> ACCELERATOR_4x = ITEMS.register(
|
||||
"4x_crafting_accelerator",
|
||||
|
|
|
|||
20
src/main/java/com/extendedae_plus/init/ModMenuTypes.java
Normal file
20
src/main/java/com/extendedae_plus/init/ModMenuTypes.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package com.extendedae_plus.init;
|
||||
|
||||
import com.extendedae_plus.ExtendedAEPlus;
|
||||
import com.extendedae_plus.menu.NetworkPatternControllerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraftforge.common.extensions.IForgeMenuType;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
public final class ModMenuTypes {
|
||||
private ModMenuTypes() {}
|
||||
|
||||
public static final DeferredRegister<MenuType<?>> MENUS =
|
||||
DeferredRegister.create(ForgeRegistries.MENU_TYPES, ExtendedAEPlus.MODID);
|
||||
|
||||
public static final RegistryObject<MenuType<NetworkPatternControllerMenu>> NETWORK_PATTERN_CONTROLLER =
|
||||
MENUS.register("network_pattern_controller",
|
||||
() -> IForgeMenuType.create(NetworkPatternControllerMenu::new));
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.extendedae_plus.integration.jade;
|
||||
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlock;
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
|
||||
import snownee.jade.api.IWailaClientRegistration;
|
||||
import snownee.jade.api.IWailaCommonRegistration;
|
||||
import snownee.jade.api.IWailaPlugin;
|
||||
import snownee.jade.api.WailaPlugin;
|
||||
|
||||
@WailaPlugin("extendedae_plus") // 你的 mod ID
|
||||
public class WirelessTransceiverJadePlugin implements IWailaPlugin {
|
||||
|
||||
@Override
|
||||
public void register(IWailaCommonRegistration registration) {
|
||||
// 注册服务端数据提供者(用于同步数据)
|
||||
registration.registerBlockDataProvider(WirelessTransceiverProvider.INSTANCE, WirelessTransceiverBlockEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerClient(IWailaClientRegistration registration) {
|
||||
// 遍历组件常量,逐一注册
|
||||
for (var component : WirelessTransceiverJadePluginComponents.values()) {
|
||||
registration.registerBlockComponent(component, WirelessTransceiverBlock.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.extendedae_plus.integration.jade;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import snownee.jade.api.BlockAccessor;
|
||||
import snownee.jade.api.IBlockComponentProvider;
|
||||
import snownee.jade.api.ITooltip;
|
||||
import snownee.jade.api.config.IPluginConfig;
|
||||
|
||||
/**
|
||||
* 单文件聚合的 Jade 组件提供者,包含五个子组件常量,分别对应五个独立的开关/UID。
|
||||
*/
|
||||
public enum WirelessTransceiverJadePluginComponents implements IBlockComponentProvider {
|
||||
FREQUENCY("wt_frequency") {
|
||||
@Override
|
||||
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
|
||||
if (data.contains("frequency")) {
|
||||
long frequency = data.getLong("frequency");
|
||||
tooltip.add(Component.translatable("extendedae_plus.tooltip.frequency", frequency));
|
||||
}
|
||||
}
|
||||
},
|
||||
MODE("wt_master_mode") {
|
||||
@Override
|
||||
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
|
||||
if (data.contains("masterMode")) {
|
||||
boolean masterMode = data.getBoolean("masterMode");
|
||||
tooltip.add(Component.translatable("extendedae_plus.tooltip.master_mode", masterMode ? "主模式" : "从模式"));
|
||||
}
|
||||
}
|
||||
},
|
||||
MASTER_LOCATION("wt_master_location") {
|
||||
@Override
|
||||
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
|
||||
if (data.contains("masterMode") && !data.getBoolean("masterMode") && data.contains("masterPos")) {
|
||||
BlockPos pos = BlockPos.of(data.getLong("masterPos"));
|
||||
String dim = data.contains("masterDim") ? data.getString("masterDim") : "";
|
||||
tooltip.add(Component.literal("主节点位置: (" + pos.getX() + ", " + pos.getY() + ", " + pos.getZ() + ")"));
|
||||
if (!dim.isEmpty()) {
|
||||
tooltip.add(Component.literal("维度: " + dim));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
LOCKED("wt_locked") {
|
||||
@Override
|
||||
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
|
||||
if (data.contains("locked")) {
|
||||
boolean locked = data.getBoolean("locked");
|
||||
tooltip.add(Component.translatable("extendedae_plus.tooltip.locked", locked ? "已锁定" : "未锁定"));
|
||||
}
|
||||
}
|
||||
},
|
||||
NETWORK_USABLE("wt_network_usable") {
|
||||
@Override
|
||||
protected void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data) {
|
||||
if (data.contains("networkUsable")) {
|
||||
boolean usable = data.getBoolean("networkUsable");
|
||||
tooltip.add(Component.literal((usable ? "设备在线" : "设备离线")));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ResourceLocation uid;
|
||||
|
||||
WirelessTransceiverJadePluginComponents(String path) {
|
||||
this.uid = new ResourceLocation("extendedae_plus", path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void appendTooltip(ITooltip tooltip, BlockAccessor accessor, IPluginConfig config) {
|
||||
CompoundTag data = accessor.getServerData();
|
||||
if (data == null) return;
|
||||
add(accessor, tooltip, config, data);
|
||||
}
|
||||
|
||||
protected abstract void add(BlockAccessor accessor, ITooltip tooltip, IPluginConfig config, CompoundTag data);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.extendedae_plus.integration.jade;
|
||||
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity;
|
||||
import com.extendedae_plus.wireless.IWirelessEndpoint;
|
||||
import com.extendedae_plus.wireless.WirelessMasterRegistry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import snownee.jade.api.BlockAccessor;
|
||||
import snownee.jade.api.IServerDataProvider;
|
||||
|
||||
public enum WirelessTransceiverProvider implements IServerDataProvider<BlockAccessor> {
|
||||
INSTANCE;
|
||||
|
||||
private static final ResourceLocation UID = new ResourceLocation("extendedae_plus", "wireless_transceiver_info");
|
||||
// 此类仅用于同步服务端数据,不再包含客户端选项键
|
||||
|
||||
@Override
|
||||
public ResourceLocation getUid() {
|
||||
return UID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendServerData(CompoundTag data, BlockAccessor accessor) {
|
||||
if (accessor.getBlockEntity() instanceof WirelessTransceiverBlockEntity blockEntity) {
|
||||
data.putLong("frequency", blockEntity.getFrequency());
|
||||
data.putBoolean("masterMode", blockEntity.isMasterMode());
|
||||
data.putBoolean("locked", blockEntity.isLocked());
|
||||
// 判断 AE 网络是否可用:节点存在、加入网路且网络通电
|
||||
IGridNode node = blockEntity.getGridNode();
|
||||
IGrid grid = node == null ? null : node.getGrid();
|
||||
boolean networkUsable = false;
|
||||
if (grid != null) {
|
||||
try {
|
||||
networkUsable = grid.getEnergyService().isNetworkPowered();
|
||||
} catch (Throwable ignored) {
|
||||
networkUsable = false;
|
||||
}
|
||||
}
|
||||
data.putBoolean("networkUsable", networkUsable);
|
||||
// 如果是从模式,查询主节点位置与维度
|
||||
if (!blockEntity.isMasterMode()) {
|
||||
var level = blockEntity.getServerLevel();
|
||||
long freq = blockEntity.getFrequency();
|
||||
IWirelessEndpoint master = WirelessMasterRegistry.get(level, freq);
|
||||
if (master != null && !master.isEndpointRemoved()) {
|
||||
BlockPos pos = master.getBlockPos();
|
||||
if (pos != null) {
|
||||
data.putLong("masterPos", pos.asLong());
|
||||
}
|
||||
if (master.getServerLevel() != null) {
|
||||
data.putString("masterDim", master.getServerLevel().dimension().location().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
package com.extendedae_plus.integration.jei;
|
||||
|
||||
import com.extendedae_plus.mixin.jei.accessor.BookmarkOverlayAccessor;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
import mezz.jei.api.runtime.IBookmarkOverlay;
|
||||
import mezz.jei.api.runtime.IIngredientListOverlay;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import mezz.jei.gui.bookmarks.BookmarkList;
|
||||
import mezz.jei.gui.overlay.elements.IElement;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
|
|
@ -113,4 +118,18 @@ public final class JeiRuntimeProxy {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JEI书签列表
|
||||
*/
|
||||
public static List<? extends ITypedIngredient<?>> getBookmarkList() {
|
||||
IJeiRuntime rt = RUNTIME;
|
||||
if (rt == null) return Collections.emptyList();
|
||||
IBookmarkOverlay bookmarkOverlay = rt.getBookmarkOverlay();
|
||||
if (bookmarkOverlay instanceof BookmarkOverlayAccessor accessor) {
|
||||
BookmarkList bookmarkList = accessor.eap$getBookmarkList();
|
||||
return bookmarkList.getElements().stream().map(IElement::getTypedIngredient).toList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package com.extendedae_plus.menu;
|
||||
|
||||
import com.extendedae_plus.init.ModMenuTypes;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public class NetworkPatternControllerMenu extends AbstractContainerMenu {
|
||||
private final BlockPos bePos;
|
||||
|
||||
public NetworkPatternControllerMenu(int id, Inventory inv, BlockPos bePos) {
|
||||
super(ModMenuTypes.NETWORK_PATTERN_CONTROLLER.get(), id);
|
||||
this.bePos = bePos;
|
||||
}
|
||||
|
||||
public NetworkPatternControllerMenu(int id, Inventory inv, FriendlyByteBuf buf) {
|
||||
this(id, inv, buf.readBlockPos());
|
||||
}
|
||||
|
||||
public BlockPos getBlockEntityPos() { return bePos; }
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) { return true; }
|
||||
|
||||
@Override
|
||||
public ItemStack quickMoveStack(Player player, int index) {
|
||||
// 无物品槽的容器,直接返回空堆以禁用快速转移
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.extendedae_plus.mixin.accessor;
|
||||
|
||||
import net.minecraft.client.gui.components.Renderable;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.narration.NarratableEntry;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(Screen.class)
|
||||
public interface ScreenInvoker {
|
||||
@Invoker("addRenderableWidget")
|
||||
<W extends GuiEventListener & Renderable & NarratableEntry> W eap$invokeAddRenderableWidget(W widget);
|
||||
}
|
||||
|
|
@ -2,21 +2,29 @@ package com.extendedae_plus.mixin.ae2;
|
|||
|
||||
import appeng.client.Point;
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import appeng.client.gui.StackWithBounds;
|
||||
import appeng.client.gui.me.crafting.CraftingCPUScreen;
|
||||
import appeng.client.gui.TextOverride;
|
||||
import appeng.client.gui.style.PaletteColor;
|
||||
import appeng.client.gui.style.ScreenStyle;
|
||||
import appeng.client.gui.style.Text;
|
||||
import appeng.client.gui.style.TextAlignment;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.menu.slot.AppEngSlot;
|
||||
import com.extendedae_plus.api.ExPatternPageAccessor;
|
||||
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
|
||||
import com.extendedae_plus.util.GuiUtil;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.TranslatableContents;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -38,6 +46,74 @@ public abstract class AEBaseScreenMixin {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+左键,
|
||||
* 读取鼠标下的 AEKey 并发送 CraftingMonitorJumpC2SPacket。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
private void eap$craftingCpuShiftLeftClick(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 仅处理 CraftingCPUScreen 实例
|
||||
Object self = this;
|
||||
if (!(self instanceof CraftingCPUScreen<?> screen)) {
|
||||
return;
|
||||
}
|
||||
// 仅在 Shift + 左键 时触发
|
||||
if (button != 0 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY);
|
||||
if (hovered == null || hovered.stack() == null) {
|
||||
return;
|
||||
}
|
||||
AEKey key = hovered.stack().what();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorJumpC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorJumpC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 AEBaseScreen 的 mouseClicked 入口拦截 CraftingCPUScreen 的 Shift+右键,
|
||||
* 读取鼠标下的 AEKey 并发送 CraftingMonitorOpenProviderC2SPacket(打开样板供应器UI)。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
private void eap$craftingCpuShiftRightClick(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 仅处理 CraftingCPUScreen 实例
|
||||
Object self = this;
|
||||
if (!(self instanceof CraftingCPUScreen<?> screen)) {
|
||||
return;
|
||||
}
|
||||
// 仅在 Shift + 右键 时触发
|
||||
if (button != 1 || !net.minecraft.client.gui.screens.Screen.hasShiftDown()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
StackWithBounds hovered = screen.getStackUnderMouse(mouseX, mouseY);
|
||||
if (hovered == null || hovered.stack() == null) {
|
||||
return;
|
||||
}
|
||||
AEKey key = hovered.stack().what();
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Debug: 标记一次发送(打开供应器UI)
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP: Send CraftingMonitorOpenProviderC2SPacket: {}", key);
|
||||
} catch (Throwable ignored2) {}
|
||||
ModNetwork.CHANNEL.sendToServer(new CraftingMonitorOpenProviderC2SPacket(key));
|
||||
cir.setReturnValue(true);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static int eap$getIntField(Object self, String name, int def) {
|
||||
Class<?> c = self.getClass();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(value = AEProcessingPattern.class, remap = false)
|
||||
public class AEProcessingPatternMixin implements SmartDoublingAwarePattern {
|
||||
@Unique
|
||||
private boolean eap$allowScaling = true; // 默认允许缩放
|
||||
|
||||
@Override
|
||||
public boolean eap$allowScaling() {
|
||||
return eap$allowScaling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eap$setAllowScaling(boolean allow) {
|
||||
this.eap$allowScaling = allow;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.extendedae_plus.mixin.jei;
|
||||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.integration.modules.jei.transfer.EncodePatternTransferHandler;
|
||||
import appeng.integration.modules.jeirei.EncodingHelper;
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.api.stacks.AEFluidKey;
|
||||
import appeng.api.stacks.AEItemKey;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.integration.modules.jeirei.EncodingHelper;
|
||||
import appeng.menu.me.common.GridInventoryEntry;
|
||||
import appeng.menu.me.common.MEStorageMenu;
|
||||
import com.extendedae_plus.integration.jei.JeiRuntimeProxy;
|
||||
import mezz.jei.api.constants.VanillaTypes;
|
||||
import mezz.jei.api.forge.ForgeTypes;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Mixin(EncodingHelper.class)
|
||||
public class EncodingHelperMixin {
|
||||
// 客户端:注入优先使用JEI书签的物品,流体
|
||||
@Inject(method = "getIngredientPriorities", at = @At("TAIL"), cancellable = true, remap = false)
|
||||
private static void epp$addJeiIngredientPriorities(MEStorageMenu menu, Comparator<GridInventoryEntry> comparator, CallbackInfoReturnable<Map<AEKey, Integer>> cir){
|
||||
Map<AEKey, Integer> result = cir.getReturnValue();
|
||||
AtomicInteger index = new AtomicInteger(Integer.MAX_VALUE);
|
||||
List<? extends ITypedIngredient<?>> list = JeiRuntimeProxy.getBookmarkList();
|
||||
for (ITypedIngredient<?> ingredient : list) {
|
||||
ingredient.getIngredient(VanillaTypes.ITEM_STACK).ifPresent(itemStack -> result.put(AEItemKey.of(itemStack), index.getAndDecrement()));
|
||||
ingredient.getIngredient(ForgeTypes.FLUID_STACK).ifPresent(fluidStack -> result.put(AEFluidKey.of(fluidStack), index.getAndDecrement()));
|
||||
}
|
||||
cir.setReturnValue(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicPatternsAccessor;
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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;
|
||||
|
||||
@Mixin(PatternProviderLogic.class)
|
||||
public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder {
|
||||
@Unique
|
||||
private static final String EPP_SMART_DOUBLING_KEY = "epp_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 = ((PatternProviderLogicPatternsAccessor) this).eap$patterns();
|
||||
for (IPatternDetails details : list) {
|
||||
if (details instanceof AEProcessingPattern proc && proc instanceof SmartDoublingAwarePattern aware) {
|
||||
aware.eap$setAllowScaling(value);
|
||||
}
|
||||
}
|
||||
// 触发一次刷新,让网络及时拿到最新状态(也会触发 ICraftingProvider.requestUpdate(mainNode))
|
||||
((PatternProviderLogic) (Object) this).updatePatterns();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "writeToNBT", at = @At("TAIL"), remap = false)
|
||||
private void eap$writeSmartDoublingToNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
tag.putBoolean(EPP_SMART_DOUBLING_KEY, this.eap$smartDoubling);
|
||||
}
|
||||
|
||||
@Inject(method = "readFromNBT", at = @At("TAIL"), remap = false)
|
||||
private void eap$readSmartDoublingFromNbt(CompoundTag tag, CallbackInfo ci) {
|
||||
if (tag.contains(EPP_SMART_DOUBLING_KEY)) {
|
||||
this.eap$smartDoubling = tag.getBoolean(EPP_SMART_DOUBLING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "updatePatterns", at = @At("TAIL"), remap = false)
|
||||
private void eap$applySmartDoublingToPatterns(CallbackInfo ci) {
|
||||
try {
|
||||
var list = ((PatternProviderLogicPatternsAccessor) 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.extendedae_plus.mixin.ae2;
|
||||
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.guisync.GuiSync;
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
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 static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
@Mixin(PatternProviderMenu.class)
|
||||
public abstract class PatternProviderMenuDoublingMixin implements PatternProviderMenuDoublingSync {
|
||||
@Shadow
|
||||
protected PatternProviderLogic logic;
|
||||
|
||||
@Unique
|
||||
@GuiSync(21)
|
||||
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();
|
||||
LOGGER.debug("[EAP] Menu broadcastChanges HEAD: eap$SmartDoubling={}", this.eap$SmartDoubling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "<init>(ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL"))
|
||||
private void eap$initSmartSync_Public(int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing smart doubling sync", t);
|
||||
}
|
||||
}
|
||||
|
||||
@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$initSmartSync_Protected(MenuType<? extends PatternProviderMenu> menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) {
|
||||
try {
|
||||
var l = this.logic;
|
||||
if (l instanceof SmartDoublingHolder holder) {
|
||||
this.eap$SmartDoubling = holder.eap$getSmartDoubling();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Error initializing smart doubling sync", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean eap$getSmartDoublingSynced() {
|
||||
return this.eap$SmartDoubling;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,10 @@ import appeng.client.gui.widgets.SettingToggleButton;
|
|||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.api.ExPatternButtonsAccessor;
|
||||
import com.extendedae_plus.api.PatternProviderMenuAdvancedSync;
|
||||
import com.extendedae_plus.api.PatternProviderMenuDoublingSync;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.ToggleAdvancedBlockingC2SPacket;
|
||||
import com.extendedae_plus.network.ToggleSmartDoublingC2SPacket;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
|
|
@ -36,6 +38,12 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
@Unique
|
||||
private boolean eap$AdvancedBlockingEnabled = false;
|
||||
|
||||
@Unique
|
||||
private SettingToggleButton<YesNo> eap$SmartDoublingToggle;
|
||||
|
||||
@Unique
|
||||
private boolean eap$SmartDoublingEnabled = false;
|
||||
|
||||
public PatternProviderScreenMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) {
|
||||
super(menu, playerInventory, title, style);
|
||||
}
|
||||
|
|
@ -76,27 +84,62 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
|
|||
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) -> {
|
||||
LOGGER.debug("[EAP] Click smart doubling toggle: send C2S");
|
||||
ModNetwork.CHANNEL.sendToServer(new ToggleSmartDoublingC2SPacket());
|
||||
}
|
||||
) {
|
||||
@Override
|
||||
public java.util.List<net.minecraft.network.chat.Component> getTooltipMessage() {
|
||||
boolean enabled = eap$SmartDoublingEnabled;
|
||||
var title = net.minecraft.network.chat.Component.literal("智能翻倍");
|
||||
var line = enabled
|
||||
? net.minecraft.network.chat.Component.literal("已启用:根据请求量对处理样板进行智能缩放")
|
||||
: net.minecraft.network.chat.Component.literal("已禁用:按原始样板数量进行发配");
|
||||
return java.util.List.of(title, line);
|
||||
}
|
||||
};
|
||||
|
||||
this.eap$SmartDoublingToggle.set(this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO);
|
||||
this.addToLeftToolbar(this.eap$SmartDoublingToggle);
|
||||
}
|
||||
|
||||
// 每帧刷新:仅从菜单(@GuiSync)同步布尔值,保持按钮状态一致
|
||||
@Inject(method = "updateBeforeRender", at = @At("HEAD"), remap = false)
|
||||
private void eap$updateAdvancedBlocking(CallbackInfo ci) {
|
||||
if (this.eap$AdvancedBlockingToggle == null) return;
|
||||
|
||||
boolean desired = this.eap$AdvancedBlockingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
if (this.eap$AdvancedBlockingToggle != null) {
|
||||
boolean desired = this.eap$AdvancedBlockingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuAdvancedSync sync) {
|
||||
desired = sync.eap$getAdvancedBlockingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (adv): desired={}", desired);
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
|
||||
// 与AE2一致:每帧无条件对齐按钮状态至@GuiSync(使用YesNo以获得原版图标与提示)
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick: desired={}", desired);
|
||||
if (this.eap$AdvancedBlockingEnabled != desired) {
|
||||
LOGGER.debug("[EAP] updateBeforeRender: desired changed {} -> {}", this.eap$AdvancedBlockingEnabled, desired);
|
||||
if (this.eap$SmartDoublingToggle != null) {
|
||||
boolean desired2 = this.eap$SmartDoublingEnabled;
|
||||
if (this.menu instanceof PatternProviderMenuDoublingSync sync2) {
|
||||
desired2 = sync2.eap$getSmartDoublingSynced();
|
||||
}
|
||||
LOGGER.debug("[EAP] updateBeforeRender tick (dbl): desired={}", desired2);
|
||||
this.eap$SmartDoublingEnabled = desired2;
|
||||
this.eap$SmartDoublingToggle.set(desired2 ? YesNo.YES : YesNo.NO);
|
||||
}
|
||||
this.eap$AdvancedBlockingEnabled = desired;
|
||||
this.eap$AdvancedBlockingToggle.set(desired ? YesNo.YES : YesNo.NO);
|
||||
|
||||
// 如果当前屏幕是 ExtendedAE 的 GuiExPatternProvider,则委托布局更新到 accessor
|
||||
if ((Object) this instanceof GuiExPatternProvider) {
|
||||
try {
|
||||
((ExPatternButtonsAccessor) this).eap$updateButtonsLayout();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.extendedae_plus.mixin.ae2.accessor;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(value = PatternProviderLogic.class, remap = false)
|
||||
public interface PatternProviderLogicPatternsAccessor {
|
||||
@Accessor("patterns")
|
||||
List<IPatternDetails> eap$patterns();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.extendedae_plus.mixin.autopattern;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.me.service.CraftingService;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
|
||||
/**
|
||||
* 在 CraftingService.getProviders 调用点修改传入的 IPatternDetails 参数(回退到网络注册的原始样板)
|
||||
*/
|
||||
@Mixin(value = CraftingService.class, remap = false)
|
||||
public class CraftingServiceGetProvidersMixin {
|
||||
|
||||
@ModifyArg(method = "getProviders(Lappeng/api/crafting/IPatternDetails;)Ljava/lang/Iterable;",
|
||||
at = @At(value = "INVOKE", target = "Lappeng/me/service/helpers/NetworkCraftingProviders;getMediums(Lappeng/api/crafting/IPatternDetails;)Ljava/lang/Iterable;"),
|
||||
index = 0)
|
||||
private IPatternDetails eap$modifyGetProvidersArg(IPatternDetails original) {
|
||||
IPatternDetails base = null;
|
||||
if (original instanceof ScaledProcessingPattern scaledProcessingPattern) {
|
||||
base = scaledProcessingPattern.getOriginal();
|
||||
}
|
||||
return base == null ? original : base;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.extendedae_plus.mixin.autopattern;
|
||||
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.crafting.CraftingTreeNode;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(CraftingTreeNode.class)
|
||||
public interface CraftingTreeNodeAccessor {
|
||||
@Accessor("what")
|
||||
AEKey eap$getWhat();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.extendedae_plus.mixin.autopattern;
|
||||
|
||||
import appeng.api.stacks.KeyCounter;
|
||||
import appeng.crafting.CraftingTreeNode;
|
||||
import appeng.crafting.inv.CraftingSimulationState;
|
||||
import com.extendedae_plus.util.RequestedAmountHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(value = CraftingTreeNode.class,remap = false)
|
||||
public class CraftingTreeNodeMixin {
|
||||
@Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lappeng/crafting/CraftingTreeNode;addContainerItems(Lappeng/api/stacks/AEKey;JLappeng/api/stacks/KeyCounter;)V"),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD)
|
||||
private void captureRequestedAmount(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) {
|
||||
// push the requestedAmount before addContainerItems is called
|
||||
RequestedAmountHolder.push(requestedAmount);
|
||||
}
|
||||
|
||||
@Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V",
|
||||
at = @At(value = "RETURN"))
|
||||
private void clearRequestedAmountOnReturn(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) {
|
||||
// pop the pushed requested amount on return
|
||||
RequestedAmountHolder.pop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.extendedae_plus.mixin.autopattern;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.crafting.ICraftingProvider;
|
||||
import appeng.api.networking.crafting.ICraftingService;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.crafting.CraftingCalculation;
|
||||
import appeng.crafting.CraftingTreeNode;
|
||||
import appeng.crafting.CraftingTreeProcess;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import appeng.me.service.CraftingService;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
import com.extendedae_plus.util.PatternScaler;
|
||||
import com.extendedae_plus.config.ModConfigs;
|
||||
import com.extendedae_plus.util.RequestedAmountHolder;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
/**
|
||||
* 注入 CraftingTreeProcess 构造器尾部:将 AEProcessingPattern 替换为 ScaledProcessingPattern
|
||||
* 以确保后续执行使用放大后的输入/输出视图。
|
||||
*/
|
||||
@Mixin(CraftingTreeProcess.class)
|
||||
public abstract class CraftingTreeProcessMixin {
|
||||
|
||||
@ModifyVariable(
|
||||
method = "<init>(Lappeng/api/networking/crafting/ICraftingService;Lappeng/crafting/CraftingCalculation;Lappeng/api/crafting/IPatternDetails;Lappeng/crafting/CraftingTreeNode;)V",
|
||||
at = @At("HEAD"),
|
||||
argsOnly = true
|
||||
)
|
||||
private static IPatternDetails eap$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) {
|
||||
try {
|
||||
// 若传入的 details 已经是缩放样板,且原始样板不允许缩放,则直接解包为原始样板
|
||||
if (details instanceof ScaledProcessingPattern sp) {
|
||||
var proc0 = sp.getOriginal();
|
||||
if (proc0 instanceof SmartDoublingAwarePattern aware0 && !aware0.eap$allowScaling()) {
|
||||
return proc0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(details instanceof AEProcessingPattern proc)) return original;
|
||||
|
||||
// 若样板标记为不允许缩放,则直接跳过
|
||||
if (proc instanceof SmartDoublingAwarePattern aware && !aware.eap$allowScaling()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode;
|
||||
AEKey parentTarget = parentAcc.eap$getWhat();
|
||||
long requested = RequestedAmountHolder.get();
|
||||
|
||||
// 根据配置决定是否在 provider 间轮询分配请求量(默认开启)
|
||||
long perProvider = 1L;
|
||||
if (!ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get()) {
|
||||
// 关闭轮询:直接使用完整请求量,不需要查询 provider 列表
|
||||
perProvider = requested;
|
||||
if (perProvider <= 0) perProvider = 1L;
|
||||
} else {
|
||||
CraftingService craftingService = (CraftingService) cc;
|
||||
Iterable<ICraftingProvider> providers = craftingService.getProviders(original);
|
||||
|
||||
// 计算 provider 数量;尝试用反射读取内部 providers 列表以避免消费迭代器
|
||||
int size;
|
||||
try {
|
||||
var cls = providers.getClass();
|
||||
var f = cls.getDeclaredField("providers"); // private ArrayList<ICraftingProvider>
|
||||
f.setAccessible(true);
|
||||
List<?> list = (List<?>) f.get(providers);
|
||||
size = list == null ? 0 : list.size();
|
||||
} catch (Exception ex) {
|
||||
// 反射失败回退为遍历计数(会消费迭代器)
|
||||
size = (int) StreamSupport.stream(providers.spliterator(), false).count();
|
||||
}
|
||||
|
||||
// 将 requested 在 providers 间均分,向上取整保证每个 provider 分配整数且总量不少于 requested
|
||||
if (size > 0) {
|
||||
perProvider = requested / size + ((requested % size) == 0 ? 0 : 1);
|
||||
if (perProvider <= 0) perProvider = 1L;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用每-provider 的分配量来缩放样板
|
||||
var scaled = PatternScaler.scale(proc, parentTarget, perProvider);
|
||||
return scaled != null ? scaled : original;
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("构建倍增样板出错", e);
|
||||
e.printStackTrace();
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.extendedae_plus.mixin.autopattern;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
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 = PatternProviderLogic.class, remap = false)
|
||||
public class PatternProviderLogicContainsRedirectMixin {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,21 @@ import com.glodblock.github.glodium.network.packet.sync.IActionHolder;
|
|||
import com.glodblock.github.glodium.network.packet.sync.Paras;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
|
@ -17,11 +32,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mixin(ContainerExPatternTerminal.class)
|
||||
public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
||||
|
||||
@GuiSync(11452)
|
||||
@GuiSync(25564)
|
||||
@Unique
|
||||
public boolean eap$hidePatternSlots = false;
|
||||
|
||||
|
|
@ -41,13 +58,19 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
|||
}
|
||||
|
||||
@Unique
|
||||
private final Map<String, Consumer<Paras>> eap$actions = createHolder();
|
||||
private Map<String, Consumer<Paras>> eap$actions;
|
||||
|
||||
@Unique
|
||||
private Player epp$player;
|
||||
|
||||
@Unique
|
||||
private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus");
|
||||
|
||||
@Inject(method = "<init>*", at = @At("TAIL"))
|
||||
private void init(int id, net.minecraft.world.entity.player.Inventory playerInventory, IConfigurableObject host, CallbackInfo ci) {
|
||||
if (this.eap$actions == null) {
|
||||
this.eap$actions = createHolder();
|
||||
}
|
||||
this.epp$player = playerInventory.player;
|
||||
// 注册上传动作:参数顺序必须与客户端 CGenericPacket 保持一致
|
||||
this.eap$actions.put("upload", p -> {
|
||||
|
|
@ -61,6 +84,110 @@ public abstract class ContainerExPatternTerminalMixin implements IActionHolder {
|
|||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
|
||||
// 注册打开UI动作:open_ui(posLong, dimensionId, faceOrdinal?)
|
||||
this.eap$actions.put("open_ui", p -> {
|
||||
try {
|
||||
// 参数解析
|
||||
Object po = p.get(0); // BlockPos as long (BlockPos#asLong)
|
||||
Object do0 = p.get(1); // Dimension id string (e.g., minecraft:overworld)
|
||||
Object fo;
|
||||
try {
|
||||
fo = p.get(2); // Optional face ordinal
|
||||
} catch (Throwable __ignored) {
|
||||
fo = null;
|
||||
}
|
||||
|
||||
long posLong = (po instanceof Number) ? ((Number) po).longValue() : Long.parseLong(String.valueOf(po));
|
||||
String dimStr = String.valueOf(do0);
|
||||
int faceOrd = -1;
|
||||
if (fo != null) {
|
||||
faceOrd = (fo instanceof Number) ? ((Number) fo).intValue() : Integer.parseInt(String.valueOf(fo));
|
||||
}
|
||||
|
||||
BlockPos pos = BlockPos.of(posLong);
|
||||
ResourceLocation dimId = ResourceLocation.tryParse(dimStr);
|
||||
if (dimId == null) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: invalid dim '{}'", dimStr);
|
||||
return;
|
||||
}
|
||||
ResourceKey<Level> dimKey = ResourceKey.create(Registries.DIMENSION, dimId);
|
||||
|
||||
if (!(this.epp$player instanceof ServerPlayer sp)) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: not a ServerPlayer");
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = sp.server.getLevel(dimKey);
|
||||
if (level == null) {
|
||||
EAP_LOGGER.warn("[EPlus] open_ui: level null for key {}", dimKey);
|
||||
return;
|
||||
}
|
||||
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: pos={}, dim={}, faceOrd={}", pos, dimKey.location(), faceOrd);
|
||||
|
||||
// 目标应为供应器所面向/连接的相邻方块,而非供应器自身
|
||||
Direction[] tries = (faceOrd >= 0 && faceOrd < Direction.values().length)
|
||||
? new Direction[]{Direction.values()[faceOrd]}
|
||||
: Direction.values();
|
||||
|
||||
// 1) 先尝试在相邻方块直接打开 MenuProvider
|
||||
for (Direction dir : tries) {
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
BlockEntity be = level.getBlockEntity(targetPos);
|
||||
if (be instanceof MenuProvider provider) {
|
||||
NetworkHooks.openScreen(sp, provider, targetPos);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: opened BE MenuProvider at {} (neighbor via {})", targetPos, dir);
|
||||
return;
|
||||
}
|
||||
var state = level.getBlockState(targetPos);
|
||||
MenuProvider provider = state.getMenuProvider(level, targetPos);
|
||||
if (provider != null) {
|
||||
NetworkHooks.openScreen(sp, provider, targetPos);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: opened State MenuProvider at {} (neighbor via {})", targetPos, dir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) 兜底:为避免误触发放置/覆盖,仅在手上至少有一只手为空时,使用 BlockState.use 进行一次“徒手交互”
|
||||
boolean hasFace = (faceOrd >= 0 && faceOrd < Direction.values().length);
|
||||
boolean anyHandEmpty = sp.getMainHandItem().isEmpty() || sp.getOffhandItem().isEmpty();
|
||||
if (anyHandEmpty) {
|
||||
InteractionHand hand = sp.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (hasFace) {
|
||||
Direction dir = Direction.values()[faceOrd];
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, sp, hand, hit);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (via {}), result={}", targetPos, dir.getOpposite(), dir, r);
|
||||
} else {
|
||||
// 无朝向:优先尝试有方块实体的邻居,否则尝试实心方块邻居,各只尝试一次
|
||||
Direction chosen = null;
|
||||
for (Direction d : Direction.values()) {
|
||||
if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; }
|
||||
}
|
||||
if (chosen == null) {
|
||||
for (Direction d : Direction.values()) {
|
||||
if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; }
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
BlockPos targetPos = pos.relative(chosen);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, sp, hand, hit);
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: fallback(state.use) at {} hit {} (auto via {}), result={}", targetPos, chosen.getOpposite(), chosen, r);
|
||||
} else {
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: no neighbor candidate for fallback (faceOrd<0)");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EAP_LOGGER.debug("[EPlus] open_ui: skip fallback (hands occupied)");
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
|
|
|||
|
|
@ -10,13 +10,22 @@ import appeng.client.gui.widgets.AETextField;
|
|||
import appeng.client.gui.widgets.IconButton;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
|
||||
import com.extendedae_plus.network.ModNetwork;
|
||||
import com.extendedae_plus.network.OpenProviderUiC2SPacket;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
|
@ -29,9 +38,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Pseudo
|
||||
@Mixin(GuiExPatternTerminal.class)
|
||||
@Mixin(value = GuiExPatternTerminal.class)
|
||||
public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu> {
|
||||
|
||||
@Unique
|
||||
|
|
@ -46,6 +59,14 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
private boolean eap$showSlots = false; // 默认显示槽位
|
||||
@Unique
|
||||
private long eap$currentlyChoicePatterProvider = -1; // 当前选择的样板供应器ID
|
||||
@Unique
|
||||
private final Map<Integer, Button> eap$openUIButtons = new HashMap<>();
|
||||
|
||||
@Unique
|
||||
private static final Logger EAP_LOGGER = LogManager.getLogger("ExtendedAE_Plus");
|
||||
|
||||
@Unique
|
||||
private boolean eap$debugLoggedOnce = false;
|
||||
@Shadow(remap = false) private AETextField searchOutField;
|
||||
@Shadow(remap = false) private AETextField searchInField;
|
||||
@Shadow(remap = false) private Set<ItemStack> matchedStack;
|
||||
|
|
@ -109,8 +130,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
|
||||
/**
|
||||
* 拦截鼠标点击事件,实现Shift+左键快速上传样板功能
|
||||
* 注意:某些整合包的 ExtendedAE 版本不在该类中覆写 mouseClicked,此处设置 require=0 以防止注入失败导致崩溃。
|
||||
*/
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true)
|
||||
@Inject(method = "mouseClicked", at = @At("HEAD"), cancellable = true, require = 0)
|
||||
private void onMouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> cir) {
|
||||
// 检查是否是左键点击 + Shift键
|
||||
if (button == 0 && hasShiftDown()) {
|
||||
|
|
@ -181,6 +203,86 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private int getIntConst(Class<?> cls, String name, int defVal) {
|
||||
try {
|
||||
var f = cls.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
return (int) f.get(null);
|
||||
} catch (Throwable t) {
|
||||
return defVal;
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void eap$tryOpenProviderUI(int rowIndex) {
|
||||
try {
|
||||
// 使用 Accessor 获取 rows,避免取到父类导致失败
|
||||
com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc =
|
||||
(com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this;
|
||||
java.util.ArrayList<?> rows = acc.getRows();
|
||||
|
||||
// 找到该分组对应的第一个 PatternContainerRecord
|
||||
Class<?> cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class;
|
||||
var byGroupField = cls.getDeclaredField("byGroup");
|
||||
byGroupField.setAccessible(true);
|
||||
Object byGroup = byGroupField.get(this); // HashMultimap<PatternContainerGroup, PatternContainerRecord>
|
||||
|
||||
Object headerRow = rows.get(rowIndex);
|
||||
var groupField = headerRow.getClass().getDeclaredField("group");
|
||||
groupField.setAccessible(true);
|
||||
Object group = groupField.get(headerRow);
|
||||
|
||||
// 调用 byGroup.get(group),再取第一个元素
|
||||
java.util.Collection<?> containers = (java.util.Collection<?>) byGroup.getClass().getMethod("get", Object.class).invoke(byGroup, group);
|
||||
if (containers == null || containers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Object firstRecord = containers.iterator().next(); // PatternContainerRecord
|
||||
long serverId = (long) firstRecord.getClass().getMethod("getServerId").invoke(firstRecord);
|
||||
|
||||
// 通过 infoMap 获取位置信息
|
||||
var infoMapField = cls.getDeclaredField("infoMap");
|
||||
infoMapField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
java.util.HashMap<Long, Object> infoMap = (java.util.HashMap<Long, Object>) infoMapField.get(this);
|
||||
Object info = infoMap.get(serverId);
|
||||
if (info == null) {
|
||||
// 无位置信息,提示
|
||||
if (this.minecraft != null && this.minecraft.player != null) {
|
||||
this.minecraft.player.displayClientMessage(Component.literal("未找到该供应器的位置信息,无法打开UI"), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// PatternProviderInfo record: pos(), face(), playerWorld()
|
||||
Object pos = info.getClass().getMethod("pos").invoke(info);
|
||||
Object face = info.getClass().getMethod("face").invoke(info); // 可能为 null(方块型供应器)
|
||||
Object playerWorld = info.getClass().getMethod("playerWorld").invoke(info);
|
||||
|
||||
// 避免对 MC 类进行反射,使用强制类型转换后直接调用方法(由 Forge 运行时重映射保证)
|
||||
long posLong = ((BlockPos) pos).asLong();
|
||||
String dimStr = ((ResourceKey<Level>) playerWorld).location().toString();
|
||||
int faceOrd = -1;
|
||||
if (face != null) {
|
||||
faceOrd = ((Direction) face).ordinal();
|
||||
}
|
||||
|
||||
// 发送我们自己的 C2S 包:OpenProviderUiC2SPacket
|
||||
try {
|
||||
ModNetwork.CHANNEL.sendToServer(new OpenProviderUiC2SPacket(
|
||||
posLong,
|
||||
new ResourceLocation(dimStr),
|
||||
faceOrd
|
||||
));
|
||||
} catch (Throwable t) {
|
||||
// 静默失败:不提示玩家
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 静默失败:不输出日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置当前选择的样板供应器ID
|
||||
*/
|
||||
|
|
@ -228,6 +330,70 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
this.addToLeftToolbar(this.eap$toggleSlotsButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理屏幕缩放(resize)后按钮位置未更新的问题:
|
||||
* - 清理并移除现有的“打开UI”按钮
|
||||
* - 尝试重置滚动条并刷新列表
|
||||
* 缩放后的下一帧,drawFG 会基于新的 leftPos/topPos 重建与定位按钮
|
||||
*/
|
||||
@Inject(method = "resize", at = @At("TAIL"), remap = false, require = 0)
|
||||
private void eap$onResize(Minecraft mc, int width, int height, CallbackInfo ci) {
|
||||
try {
|
||||
// 移除并清理按钮,避免旧位置残留
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
|
||||
// 重置一次滚动条,避免可见行/偏移在缩放后与 UI 尺寸不一致
|
||||
try {
|
||||
Method resetScrollbarMethod = null;
|
||||
try {
|
||||
resetScrollbarMethod = this.getClass().getDeclaredMethod("resetScrollbar");
|
||||
} catch (NoSuchMethodException e1) {
|
||||
try {
|
||||
resetScrollbarMethod = this.getClass().getSuperclass().getDeclaredMethod("resetScrollbar");
|
||||
} catch (NoSuchMethodException e2) {
|
||||
resetScrollbarMethod = null;
|
||||
}
|
||||
}
|
||||
if (resetScrollbarMethod != null) {
|
||||
resetScrollbarMethod.setAccessible(true);
|
||||
resetScrollbarMethod.invoke(this);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 刷新列表,使 rows/visibleRows 立即以新尺寸重算
|
||||
try {
|
||||
Method refreshMethod = null;
|
||||
try {
|
||||
refreshMethod = this.getClass().getDeclaredMethod("refreshList");
|
||||
} catch (NoSuchMethodException e1) {
|
||||
try {
|
||||
refreshMethod = this.getClass().getSuperclass().getDeclaredMethod("refreshList");
|
||||
} catch (NoSuchMethodException e2) {
|
||||
refreshMethod = null;
|
||||
}
|
||||
}
|
||||
if (refreshMethod != null) {
|
||||
refreshMethod.setAccessible(true);
|
||||
refreshMethod.invoke(this);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 下次绘制重新输出一次调试行,便于确认缩放后的 rows/scroll
|
||||
this.eap$debugLoggedOnce = false;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "init", at = @At("TAIL"), remap = false, require = 0)
|
||||
private void eap$onInit(CallbackInfo ci) {
|
||||
// 清理旧的打开UI按钮
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "refreshList", at = @At("HEAD"), remap = false)
|
||||
private void onRefreshListStart(CallbackInfo ci) {
|
||||
// 更新按钮图标
|
||||
|
|
@ -236,6 +402,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
this.eap$showSlots ? "gui.expatternprovider.hide_slots" : "gui.expatternprovider.show_slots"
|
||||
)));
|
||||
}
|
||||
// 清理旧的打开UI按钮
|
||||
this.eap$openUIButtons.values().forEach(this::removeWidget);
|
||||
this.eap$openUIButtons.clear();
|
||||
}
|
||||
|
||||
@Inject(method = "refreshList", at = @At("TAIL"), remap = false)
|
||||
|
|
@ -342,6 +511,63 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
|
|||
|
||||
@Inject(method = "drawFG", at = @At("TAIL"), remap = false)
|
||||
private void eap$afterDrawFG(GuiGraphics guiGraphics, int offsetX, int offsetY, int mouseX, int mouseY, CallbackInfo ci) {
|
||||
// 动态放置/创建每个组标题后的“打开UI”按钮
|
||||
try {
|
||||
// 使用 Accessor 获取必要的字段,避免反射失败
|
||||
com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor acc =
|
||||
(com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor) (Object) this;
|
||||
java.util.ArrayList<?> rows = acc.getRows();
|
||||
int currentScroll = acc.getScrollbar().getCurrentScroll();
|
||||
|
||||
// 直接引用目标类以获取其静态常量
|
||||
Class<?> cls = com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal.class;
|
||||
int GUI_PADDING_X = getIntConst(cls, "GUI_PADDING_X", 22);
|
||||
int GUI_PADDING_Y = getIntConst(cls, "GUI_PADDING_Y", 6);
|
||||
int GUI_HEADER_HEIGHT = getIntConst(cls, "GUI_HEADER_HEIGHT", 51);
|
||||
int ROW_HEIGHT = getIntConst(cls, "ROW_HEIGHT", 18);
|
||||
int TEXT_MAX_WIDTH = getIntConst(cls, "TEXT_MAX_WIDTH", 155);
|
||||
|
||||
int visibleRows = acc.getVisibleRows();
|
||||
|
||||
// 生产环境移除调试日志
|
||||
|
||||
// 先隐藏旧按钮,避免残留
|
||||
for (Button b : this.eap$openUIButtons.values()) {
|
||||
b.visible = false;
|
||||
}
|
||||
|
||||
int shownCount = 0;
|
||||
for (int i = 0; i < visibleRows; i++) {
|
||||
int rowIndex = currentScroll + i;
|
||||
if (rowIndex < 0 || rowIndex >= rows.size()) {
|
||||
continue;
|
||||
}
|
||||
Object row = rows.get(rowIndex);
|
||||
if (!row.getClass().getSimpleName().equals("GroupHeaderRow")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 放置按钮:位于名称文本右侧,与原类 choiceButton 锚点相邻,向右偏移 20px
|
||||
int bx = this.leftPos + GUI_PADDING_X + TEXT_MAX_WIDTH - 40;
|
||||
int by = this.topPos + GUI_PADDING_Y + GUI_HEADER_HEIGHT + i * ROW_HEIGHT - 3;
|
||||
|
||||
Button btn = eap$openUIButtons.get(rowIndex);
|
||||
if (btn == null) {
|
||||
btn = Button.builder(Component.literal("UI"), (b) -> {
|
||||
eap$tryOpenProviderUI(rowIndex);
|
||||
}).size(14, 12).build();
|
||||
btn.setTooltip(Tooltip.create(Component.literal("打开该供应器目标容器的界面")));
|
||||
eap$openUIButtons.put(rowIndex, btn);
|
||||
this.addRenderableWidget(btn);
|
||||
}
|
||||
btn.setPosition(bx, by);
|
||||
btn.visible = true;
|
||||
shownCount++;
|
||||
}
|
||||
// 生产环境移除调试日志
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
// 原有的搜索高亮逻辑
|
||||
// 仅当任一搜索框非空时绘制叠加层(与原版行为保持一致)
|
||||
boolean searchActive = (this.searchOutField != null && !this.searchOutField.getValue().isEmpty())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.extendedae_plus.mixin.jei.accessor;
|
||||
|
||||
import mezz.jei.gui.bookmarks.BookmarkList;
|
||||
import mezz.jei.gui.overlay.bookmarks.BookmarkOverlay;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(BookmarkOverlay.class)
|
||||
public interface BookmarkOverlayAccessor {
|
||||
@Accessor("bookmarkList")
|
||||
BookmarkList eap$getBookmarkList();
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.crafting.ICraftingProvider;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.me.service.CraftingService;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。
|
||||
* 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器,
|
||||
* 尝试打开其目标机器的 GUI。
|
||||
*/
|
||||
public class CraftingMonitorJumpC2SPacket {
|
||||
private final AEKey what;
|
||||
|
||||
public CraftingMonitorJumpC2SPacket(AEKey what) {
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
public static void encode(CraftingMonitorJumpC2SPacket msg, FriendlyByteBuf buf) {
|
||||
AEKey.writeKey(buf, msg.what);
|
||||
}
|
||||
|
||||
public static CraftingMonitorJumpC2SPacket decode(FriendlyByteBuf buf) {
|
||||
AEKey key = AEKey.readKey(buf);
|
||||
return new CraftingMonitorJumpC2SPacket(key);
|
||||
}
|
||||
|
||||
public static void handle(CraftingMonitorJumpC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorJumpC2SPacket key={} from {}", msg.what, player.getGameProfile().getName());
|
||||
|
||||
// 必须在 CraftingCPU 界面内
|
||||
if (!(player.containerMenu instanceof appeng.menu.me.crafting.CraftingCPUMenu menu)) {
|
||||
LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过菜单 target(可能是 BlockEntity/Part/ItemHost)按 IActionHost 获取 Grid
|
||||
IGrid grid = null;
|
||||
Object target = ((appeng.menu.AEBaseMenu) menu).getTarget();
|
||||
if (target instanceof IActionHost host && host.getActionableNode() != null) {
|
||||
grid = host.getActionableNode().getGrid();
|
||||
}
|
||||
if (grid == null) {
|
||||
LogUtils.getLogger().info("EAP[S]: grid is null, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
var cs = grid.getCraftingService();
|
||||
if (!(cs instanceof CraftingService craftingService)) {
|
||||
LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) 根据 AEKey 找到可能的样板(pattern)
|
||||
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what);
|
||||
LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what);
|
||||
if (patterns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 遍历提供该样板的 Provider,优先 PatternProviderLogic
|
||||
for (var pattern : patterns) {
|
||||
var providers = craftingService.getProviders(pattern);
|
||||
int providerCount = 0;
|
||||
for (var provider : providers) {
|
||||
providerCount++;
|
||||
try {
|
||||
LogUtils.getLogger().info("EAP[S]: provider class={}", provider.getClass().getName());
|
||||
} catch (Throwable ignored) {}
|
||||
if (provider instanceof PatternProviderLogic ppl) {
|
||||
// 使用 accessor 获取 host(受保护字段通过 accessor 访问)
|
||||
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
|
||||
if (host == null) continue;
|
||||
var pbe = host.getBlockEntity();
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
|
||||
// 尝试对邻居打开 GUI(复用 OpenProviderUiC2SPacket 的策略)
|
||||
for (Direction dir : host.getTargets()) {
|
||||
BlockPos targetPos = pbe.getBlockPos().relative(dir);
|
||||
var tbe = serverLevel.getBlockEntity(targetPos);
|
||||
if (tbe instanceof MenuProvider provider1) {
|
||||
LogUtils.getLogger().info("EAP[S]: open screen via MenuProvider at {}", targetPos);
|
||||
NetworkHooks.openScreen(player, provider1, targetPos);
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
var tstate = serverLevel.getBlockState(targetPos);
|
||||
var provider2 = tstate.getMenuProvider(serverLevel, targetPos);
|
||||
if (provider2 != null) {
|
||||
LogUtils.getLogger().info("EAP[S]: open screen via state.getMenuProvider at {}", targetPos);
|
||||
NetworkHooks.openScreen(player, provider2, targetPos);
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 兜底:若无 MenuProvider,始终模拟一次右键(优先有方块实体的一面)
|
||||
InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.MAIN_HAND;
|
||||
Direction chosen = null;
|
||||
for (Direction d : host.getTargets()) {
|
||||
if (serverLevel.getBlockEntity(pbe.getBlockPos().relative(d)) != null) { chosen = d; break; }
|
||||
}
|
||||
if (chosen == null) {
|
||||
for (Direction d : host.getTargets()) {
|
||||
if (!serverLevel.getBlockState(pbe.getBlockPos().relative(d)).isAir()) { chosen = d; break; }
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
BlockPos targetPos = pbe.getBlockPos().relative(chosen);
|
||||
var state2 = serverLevel.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(serverLevel, player, hand, hit);
|
||||
LogUtils.getLogger().info("EAP[S]: simulated use on {}, face={}, result={}", targetPos, chosen, r);
|
||||
if (r.consumesAction()) {
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LogUtils.getLogger().info("EAP[S]: providers count for one pattern: {}", providerCount);
|
||||
}
|
||||
LogUtils.getLogger().info("EAP[S]: no target opened for key={}", msg.what);
|
||||
});
|
||||
context.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.me.service.CraftingService;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.me.crafting.CraftingCPUMenu;
|
||||
import appeng.menu.locator.MenuLocators;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 客户端从 CraftingCPUScreen 发送:鼠标下条目对应的 AEKey。
|
||||
* 服务端在当前打开的 CraftingCPUMenu 所属网络中,定位匹配该 AEKey 的样板供应器,
|
||||
* 打开该供应器自身的 UI(不是目标机器的 UI)。
|
||||
*/
|
||||
public class CraftingMonitorOpenProviderC2SPacket {
|
||||
private final AEKey what;
|
||||
|
||||
public CraftingMonitorOpenProviderC2SPacket(AEKey what) {
|
||||
this.what = what;
|
||||
}
|
||||
|
||||
public static void encode(CraftingMonitorOpenProviderC2SPacket msg, FriendlyByteBuf buf) {
|
||||
AEKey.writeKey(buf, msg.what);
|
||||
}
|
||||
|
||||
public static CraftingMonitorOpenProviderC2SPacket decode(FriendlyByteBuf buf) {
|
||||
AEKey key = AEKey.readKey(buf);
|
||||
return new CraftingMonitorOpenProviderC2SPacket(key);
|
||||
}
|
||||
|
||||
public static void handle(CraftingMonitorOpenProviderC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: recv CraftingMonitorOpenProviderC2SPacket key={} from {}", msg.what, player.getGameProfile().getName());
|
||||
|
||||
// 必须在 CraftingCPU 界面内
|
||||
if (!(player.containerMenu instanceof CraftingCPUMenu menu)) {
|
||||
LogUtils.getLogger().info("EAP[S]: not in CraftingCPUMenu, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过菜单的 target(可能是 BlockEntity/Part/ItemHost),按 IActionHost 获取 Grid
|
||||
IGrid grid = null;
|
||||
Object target = ((AEBaseMenu) menu).getTarget();
|
||||
if (target instanceof IActionHost host && host.getActionableNode() != null) {
|
||||
grid = host.getActionableNode().getGrid();
|
||||
}
|
||||
if (grid == null) {
|
||||
LogUtils.getLogger().info("EAP[S]: grid is null, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
var cs = grid.getCraftingService();
|
||||
if (!(cs instanceof CraftingService craftingService)) {
|
||||
LogUtils.getLogger().info("EAP[S]: craftingService is null/unsupported, abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) 根据 AEKey 找到可能的样板(pattern)
|
||||
Collection<IPatternDetails> patterns = craftingService.getCraftingFor(msg.what);
|
||||
LogUtils.getLogger().info("EAP[S]: patterns found={} for key={}", patterns.size(), msg.what);
|
||||
if (patterns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) 遍历提供该样板的 Provider,定位 PatternProviderLogic
|
||||
for (var pattern : patterns) {
|
||||
var providers = craftingService.getProviders(pattern);
|
||||
for (var provider : providers) {
|
||||
if (provider instanceof PatternProviderLogic ppl) {
|
||||
// accessor 获取 host
|
||||
PatternProviderLogicHost host = ((PatternProviderLogicAccessor) ppl).eap$host();
|
||||
if (host == null) continue;
|
||||
var pbe = host.getBlockEntity();
|
||||
if (pbe == null) continue;
|
||||
// 在服务端上下文中执行,pbe 仅用于构造菜单定位器
|
||||
|
||||
// 直接打开供应器自身的 UI(调用 Host 默认方法)
|
||||
try {
|
||||
// 部件与方块实体分别选择定位器
|
||||
if (host instanceof appeng.parts.AEBasePart part) {
|
||||
host.openMenu(player, MenuLocators.forPart(part));
|
||||
} else {
|
||||
host.openMenu(player, MenuLocators.forBlockEntity(pbe));
|
||||
}
|
||||
context.setPacketHandled(true);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
LogUtils.getLogger().error("EAP[S]: open provider UI failed at {}", pbe.getBlockPos(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.getLogger().info("EAP[S]: no provider UI opened for key={}", msg.what);
|
||||
});
|
||||
context.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.api.config.Settings;
|
||||
import appeng.api.config.YesNo;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.blockentity.crafting.PatternProviderBlockEntity;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogic;
|
||||
import appeng.helpers.patternprovider.PatternProviderLogicHost;
|
||||
import appeng.parts.crafting.PatternProviderPart;
|
||||
import com.extendedae_plus.api.AdvancedBlockingHolder;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S:全网批量切换样板供应器的三种模式:
|
||||
* - 阻挡模式(AE2 内置 BLOCKING_MODE 设置)
|
||||
* - 高级阻挡模式(AdvancedBlockingHolder mixin)
|
||||
* - 智能翻倍模式(SmartDoublingHolder mixin)
|
||||
*
|
||||
* 负载为三个操作码(各1字节),分别对应:blocking、advancedBlocking、smartDoubling。
|
||||
*/
|
||||
public class GlobalToggleProviderModesC2SPacket {
|
||||
public enum Op {
|
||||
NOOP((byte) 0),
|
||||
SET_TRUE((byte) 1),
|
||||
SET_FALSE((byte) 2),
|
||||
TOGGLE((byte) 3);
|
||||
public final byte id;
|
||||
Op(byte id) { this.id = id; }
|
||||
public static Op byId(byte id) {
|
||||
return switch (id) {
|
||||
case 1 -> SET_TRUE;
|
||||
case 2 -> SET_FALSE;
|
||||
case 3 -> TOGGLE;
|
||||
default -> NOOP;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private final Op opBlocking;
|
||||
private final Op opAdvancedBlocking;
|
||||
private final Op opSmartDoubling;
|
||||
private final BlockPos controllerPos;
|
||||
|
||||
public GlobalToggleProviderModesC2SPacket(Op opBlocking, Op opAdvancedBlocking, Op opSmartDoubling, BlockPos controllerPos) {
|
||||
this.opBlocking = opBlocking;
|
||||
this.opAdvancedBlocking = opAdvancedBlocking;
|
||||
this.opSmartDoubling = opSmartDoubling;
|
||||
this.controllerPos = controllerPos;
|
||||
}
|
||||
|
||||
public static void encode(GlobalToggleProviderModesC2SPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeByte(msg.opBlocking.id);
|
||||
buf.writeByte(msg.opAdvancedBlocking.id);
|
||||
buf.writeByte(msg.opSmartDoubling.id);
|
||||
buf.writeBlockPos(msg.controllerPos);
|
||||
}
|
||||
|
||||
public static GlobalToggleProviderModesC2SPacket decode(FriendlyByteBuf buf) {
|
||||
Op b = Op.byId(buf.readByte());
|
||||
Op ab = Op.byId(buf.readByte());
|
||||
Op sd = Op.byId(buf.readByte());
|
||||
BlockPos pos = buf.readBlockPos();
|
||||
return new GlobalToggleProviderModesC2SPacket(b, ab, sd, pos);
|
||||
}
|
||||
|
||||
public static void handle(GlobalToggleProviderModesC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
// 从控制方块实体的 AE2 节点确定 AE 网络上下文
|
||||
var level = player.serverLevel();
|
||||
var be = level.getBlockEntity(msg.controllerPos);
|
||||
if (!(be instanceof NetworkPatternControllerBlockEntity controller)) return;
|
||||
var node = controller.getGridNode(null);
|
||||
if (node == null) return;
|
||||
IGrid grid = node.getGrid();
|
||||
if (grid == null) return;
|
||||
|
||||
int affected = applyToAllProviders(grid, msg);
|
||||
// 向发起玩家反馈影响数量,便于判断按钮是否生效
|
||||
player.displayClientMessage(Component.literal("E+ 全局切换已应用到 " + affected + " 个样板供应器"), true);
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
|
||||
private static int applyToAllProviders(IGrid grid, GlobalToggleProviderModesC2SPacket msg) {
|
||||
int affected = 0;
|
||||
// 去重集合,避免同一逻辑重复计数
|
||||
Set<PatternProviderLogic> all = new HashSet<>();
|
||||
|
||||
// 方块形式的样板供应器(全部/在线)
|
||||
try {
|
||||
Set<PatternProviderBlockEntity> blocksAll = grid.getMachines(PatternProviderBlockEntity.class);
|
||||
Set<PatternProviderBlockEntity> blocksActive = grid.getActiveMachines(PatternProviderBlockEntity.class);
|
||||
for (PatternProviderBlockEntity be : blocksAll) if (be != null && be.getLogic() != null) all.add(be.getLogic());
|
||||
for (PatternProviderBlockEntity be : blocksActive) if (be != null && be.getLogic() != null) all.add(be.getLogic());
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
// Part 形式的样板供应器(全部/在线)
|
||||
try {
|
||||
Set<PatternProviderPart> partsAll = grid.getMachines(PatternProviderPart.class);
|
||||
Set<PatternProviderPart> partsActive = grid.getActiveMachines(PatternProviderPart.class);
|
||||
for (PatternProviderPart part : partsAll) if (part != null && part.getLogic() != null) all.add(part.getLogic());
|
||||
for (PatternProviderPart part : partsActive) if (part != null && part.getLogic() != null) all.add(part.getLogic());
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
// 兼容:任意实现了 PatternProviderLogicHost 的机器(例如 ExtendedAE 的 PartExPatternProvider)
|
||||
try {
|
||||
Set<PatternProviderLogicHost> hostsAll = grid.getMachines(PatternProviderLogicHost.class);
|
||||
Set<PatternProviderLogicHost> hostsActive = grid.getActiveMachines(PatternProviderLogicHost.class);
|
||||
for (PatternProviderLogicHost host : hostsAll) if (host != null && host.getLogic() != null) all.add(host.getLogic());
|
||||
for (PatternProviderLogicHost host : hostsActive) if (host != null && host.getLogic() != null) all.add(host.getLogic());
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
// 兼容:显式匹配第三方具体类(通过反射),避免 AE2 仅按精确类型匹配导致 interface 不返回的问题
|
||||
collectByClassName(grid, all, "com.glodblock.github.extendedae.common.parts.PartExPatternProvider");
|
||||
collectByClassName(grid, all, "com.glodblock.github.extendedae.common.tileentities.TileExPatternProvider");
|
||||
|
||||
for (PatternProviderLogic logic : all) {
|
||||
if (applyToLogic(logic, msg)) affected++;
|
||||
}
|
||||
return affected;
|
||||
}
|
||||
|
||||
private static void collectByClassName(IGrid grid, Set<PatternProviderLogic> out, String className) {
|
||||
try {
|
||||
Class<?> cls = Class.forName(className);
|
||||
// 收集全部与在线两类机器
|
||||
Set<?> all = grid.getMachines((Class) cls);
|
||||
Set<?> active = grid.getActiveMachines((Class) cls);
|
||||
for (Object o : all) addLogicIfPresent(out, o);
|
||||
for (Object o : active) addLogicIfPresent(out, o);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
private static void addLogicIfPresent(Set<PatternProviderLogic> out, Object o) {
|
||||
try {
|
||||
if (o instanceof PatternProviderLogicHost host) {
|
||||
var logic = host.getLogic();
|
||||
if (logic != null) out.add(logic);
|
||||
return;
|
||||
}
|
||||
// 兜底:若对象有 getLogic 方法且返回 PatternProviderLogic
|
||||
var m = o.getClass().getMethod("getLogic");
|
||||
Object ret = m.invoke(o);
|
||||
if (ret instanceof PatternProviderLogic logic) out.add(logic);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
private static boolean applyToLogic(PatternProviderLogic logic, GlobalToggleProviderModesC2SPacket msg) {
|
||||
if (logic == null) return false;
|
||||
boolean changed = false;
|
||||
// 1) 阻挡模式(AE2 内置设置)
|
||||
if (msg.opBlocking != Op.NOOP) {
|
||||
boolean current = safeIsBlocking(logic);
|
||||
boolean target = computeTarget(current, msg.opBlocking);
|
||||
var cm = logic.getConfigManager();
|
||||
if (cm != null) {
|
||||
cm.putSetting(Settings.BLOCKING_MODE, target ? YesNo.YES : YesNo.NO);
|
||||
changed = changed || (current != target);
|
||||
}
|
||||
}
|
||||
// 2) 高级阻挡(mixin 接口)
|
||||
if (msg.opAdvancedBlocking != Op.NOOP && logic instanceof AdvancedBlockingHolder adv) {
|
||||
boolean current = adv.eap$getAdvancedBlocking();
|
||||
boolean target = computeTarget(current, msg.opAdvancedBlocking);
|
||||
adv.eap$setAdvancedBlocking(target);
|
||||
changed = changed || (current != target);
|
||||
}
|
||||
// 3) 智能翻倍(mixin 接口)
|
||||
if (msg.opSmartDoubling != Op.NOOP && logic instanceof SmartDoublingHolder sd) {
|
||||
boolean current = sd.eap$getSmartDoubling();
|
||||
boolean target = computeTarget(current, msg.opSmartDoubling);
|
||||
sd.eap$setSmartDoubling(target);
|
||||
changed = changed || (current != target);
|
||||
}
|
||||
// 保存更改并让 AE2 同步
|
||||
if (changed) {
|
||||
try { logic.saveChanges(); } catch (Throwable ignored) {}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static boolean computeTarget(boolean current, Op op) {
|
||||
return switch (op) {
|
||||
case SET_TRUE -> true;
|
||||
case SET_FALSE -> false;
|
||||
case TOGGLE -> !current;
|
||||
default -> current;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean safeIsBlocking(PatternProviderLogic logic) {
|
||||
try { return logic.isBlocking(); } catch (Throwable t) { return false; }
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,12 @@ public class ModNetwork {
|
|||
private static int id = 0;
|
||||
|
||||
public static void register() {
|
||||
CHANNEL.messageBuilder(OpenProviderUiC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(OpenProviderUiC2SPacket::encode)
|
||||
.decoder(OpenProviderUiC2SPacket::decode)
|
||||
.consumerNetworkThread(OpenProviderUiC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(PickFromWirelessC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(PickFromWirelessC2SPacket::encode)
|
||||
.decoder(PickFromWirelessC2SPacket::decode)
|
||||
|
|
@ -60,11 +66,35 @@ public class ModNetwork {
|
|||
.consumerNetworkThread(ToggleAdvancedBlockingC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(ToggleSmartDoublingC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(ToggleSmartDoublingC2SPacket::encode)
|
||||
.decoder(ToggleSmartDoublingC2SPacket::decode)
|
||||
.consumerNetworkThread(ToggleSmartDoublingC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(GlobalToggleProviderModesC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(GlobalToggleProviderModesC2SPacket::encode)
|
||||
.decoder(GlobalToggleProviderModesC2SPacket::decode)
|
||||
.consumerNetworkThread(GlobalToggleProviderModesC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(AdvancedBlockingSyncS2CPacket.class, nextId(), NetworkDirection.PLAY_TO_CLIENT)
|
||||
.encoder(AdvancedBlockingSyncS2CPacket::encode)
|
||||
.decoder(AdvancedBlockingSyncS2CPacket::decode)
|
||||
.consumerNetworkThread(AdvancedBlockingSyncS2CPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(CraftingMonitorJumpC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(CraftingMonitorJumpC2SPacket::encode)
|
||||
.decoder(CraftingMonitorJumpC2SPacket::decode)
|
||||
.consumerNetworkThread(CraftingMonitorJumpC2SPacket::handle)
|
||||
.add();
|
||||
|
||||
CHANNEL.messageBuilder(CraftingMonitorOpenProviderC2SPacket.class, nextId(), NetworkDirection.PLAY_TO_SERVER)
|
||||
.encoder(CraftingMonitorOpenProviderC2SPacket::encode)
|
||||
.decoder(CraftingMonitorOpenProviderC2SPacket::decode)
|
||||
.consumerNetworkThread(CraftingMonitorOpenProviderC2SPacket::handle)
|
||||
.add();
|
||||
}
|
||||
|
||||
private static int nextId() { return id++; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OpenProviderUiC2SPacket {
|
||||
private final long posLong;
|
||||
private final ResourceLocation dimId;
|
||||
private final int faceOrd; // 目前保留,若目标需要可用
|
||||
|
||||
public OpenProviderUiC2SPacket(long posLong, ResourceLocation dimId, int faceOrd) {
|
||||
this.posLong = posLong;
|
||||
this.dimId = dimId;
|
||||
this.faceOrd = faceOrd;
|
||||
}
|
||||
|
||||
public static void encode(OpenProviderUiC2SPacket msg, FriendlyByteBuf buf) {
|
||||
buf.writeLong(msg.posLong);
|
||||
buf.writeResourceLocation(msg.dimId);
|
||||
buf.writeVarInt(msg.faceOrd);
|
||||
}
|
||||
|
||||
public static OpenProviderUiC2SPacket decode(FriendlyByteBuf buf) {
|
||||
long posLong = buf.readLong();
|
||||
ResourceLocation dimId = buf.readResourceLocation();
|
||||
int faceOrd = buf.readVarInt();
|
||||
return new OpenProviderUiC2SPacket(posLong, dimId, faceOrd);
|
||||
|
||||
}
|
||||
|
||||
public static void handle(OpenProviderUiC2SPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
ServerPlayer player = context.getSender();
|
||||
if (player == null) return;
|
||||
|
||||
|
||||
// 校验维度与方块
|
||||
ResourceKey<Level> levelKey = ResourceKey.create(Registries.DIMENSION, msg.dimId);
|
||||
ServerLevel level = player.server.getLevel(levelKey);
|
||||
if (level == null) {
|
||||
return; // 无效维度
|
||||
}
|
||||
|
||||
BlockPos pos = BlockPos.of(msg.posLong);
|
||||
if (!level.isLoaded(pos)) {
|
||||
return; // 区块未加载
|
||||
}
|
||||
|
||||
var be = level.getBlockEntity(pos);
|
||||
var stateAtPos = level.getBlockState(pos);
|
||||
|
||||
// 目标通常是供应器所面对/连接的“相邻方块”,优先尝试邻居
|
||||
Direction[] tries = (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length)
|
||||
? new Direction[]{Direction.values()[msg.faceOrd]}
|
||||
: Direction.values();
|
||||
|
||||
for (Direction dir : tries) {
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
BlockEntity tbe = level.getBlockEntity(targetPos);
|
||||
if (tbe instanceof MenuProvider provider) {
|
||||
NetworkHooks.openScreen(player, provider, targetPos);
|
||||
return;
|
||||
}
|
||||
var tstate = level.getBlockState(targetPos);
|
||||
MenuProvider provider2 = tstate.getMenuProvider(level, targetPos);
|
||||
if (provider2 != null) {
|
||||
NetworkHooks.openScreen(player, provider2, targetPos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果邻居也未提供 MenuProvider,则兜底:尽量模拟一次徒手右键相邻方块
|
||||
boolean anyHandEmpty = player.getMainHandItem().isEmpty() || player.getOffhandItem().isEmpty();
|
||||
if (anyHandEmpty) {
|
||||
InteractionHand hand = player.getMainHandItem().isEmpty() ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND;
|
||||
if (msg.faceOrd >= 0 && msg.faceOrd < Direction.values().length) {
|
||||
Direction dir = Direction.values()[msg.faceOrd];
|
||||
BlockPos targetPos = pos.relative(dir);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), dir.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, player, hand, hit);
|
||||
if (r.consumesAction()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 无明确朝向:优先挑选有方块实体的邻居,否则挑选非空气方块
|
||||
Direction chosen = null;
|
||||
for (Direction d : Direction.values()) {
|
||||
if (level.getBlockEntity(pos.relative(d)) != null) { chosen = d; break; }
|
||||
}
|
||||
if (chosen == null) {
|
||||
for (Direction d : Direction.values()) {
|
||||
if (!level.getBlockState(pos.relative(d)).isAir()) { chosen = d; break; }
|
||||
}
|
||||
}
|
||||
if (chosen != null) {
|
||||
BlockPos targetPos = pos.relative(chosen);
|
||||
var state2 = level.getBlockState(targetPos);
|
||||
var hit = new BlockHitResult(Vec3.atCenterOf(targetPos), chosen.getOpposite(), targetPos, false);
|
||||
InteractionResult r = state2.use(level, player, hand, hit);
|
||||
if (r.consumesAction()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 无可选邻居
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 双手占用则跳过兜底交互
|
||||
}
|
||||
|
||||
context.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.extendedae_plus.network;
|
||||
|
||||
import appeng.menu.implementations.PatternProviderMenu;
|
||||
import com.extendedae_plus.api.SmartDoublingHolder;
|
||||
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderMenuAdvancedAccessor;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* C2S:切换智能翻倍启用状态。
|
||||
* 不含额外负载,基于玩家当前打开的 PatternProviderMenu 进行切换。
|
||||
*/
|
||||
public class ToggleSmartDoublingC2SPacket {
|
||||
public ToggleSmartDoublingC2SPacket() {}
|
||||
|
||||
public static void encode(ToggleSmartDoublingC2SPacket msg, FriendlyByteBuf buf) {}
|
||||
|
||||
public static ToggleSmartDoublingC2SPacket decode(FriendlyByteBuf buf) {
|
||||
return new ToggleSmartDoublingC2SPacket();
|
||||
}
|
||||
|
||||
public static void handle(ToggleSmartDoublingC2SPacket msg, Supplier<NetworkEvent.Context> ctxSupplier) {
|
||||
var ctx = ctxSupplier.get();
|
||||
ctx.enqueueWork(() -> {
|
||||
ServerPlayer player = ctx.getSender();
|
||||
if (player == null) return;
|
||||
if (!(player.containerMenu instanceof PatternProviderMenu menu)) return;
|
||||
|
||||
var accessor = (PatternProviderMenuAdvancedAccessor) menu;
|
||||
var logic = accessor.eap$logic();
|
||||
if (logic instanceof SmartDoublingHolder holder) {
|
||||
boolean current = holder.eap$getSmartDoubling();
|
||||
boolean next = !current;
|
||||
holder.eap$setSmartDoubling(next);
|
||||
logic.saveChanges();
|
||||
}
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
}
|
||||
108
src/main/java/com/extendedae_plus/util/PatternScaler.java
Normal file
108
src/main/java/com/extendedae_plus/util/PatternScaler.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import appeng.api.crafting.IPatternDetails.IInput;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.crafting.pattern.AEProcessingPattern;
|
||||
import com.extendedae_plus.content.ScaledProcessingPattern;
|
||||
import com.extendedae_plus.api.SmartDoublingAwarePattern;
|
||||
|
||||
|
||||
import static com.extendedae_plus.util.ExtendedAELogger.LOGGER;
|
||||
|
||||
public final class PatternScaler {
|
||||
private PatternScaler() {
|
||||
}
|
||||
|
||||
public static ScaledProcessingPattern scale(AEProcessingPattern base, AEKey target, long requestedAmount) {
|
||||
if (base == null) throw new IllegalArgumentException("base");
|
||||
if (target == null) throw new IllegalArgumentException("target");
|
||||
|
||||
// 双保险:若样板标记为不允许缩放,直接放弃缩放(返回 null 表示调用方应保持原样板)
|
||||
if (base instanceof SmartDoublingAwarePattern aware && !aware.eap$allowScaling()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GenericStack[] baseSparseInputs = base.getSparseInputs();
|
||||
GenericStack[] baseSparseOutputs = base.getSparseOutputs();
|
||||
IInput[] baseInputs = base.getInputs();
|
||||
GenericStack[] baseOutputs = base.getOutputs();
|
||||
|
||||
// 新逻辑:不再对样板进行单位化处理
|
||||
// 找到目标输出在 outputs 中的索引(尝试匹配 target,否则取第一个非空输出)
|
||||
int targetOutIndex = -1;
|
||||
for (int i = 0; i < baseOutputs.length; i++) {
|
||||
var out = baseOutputs[i];
|
||||
if (out != null && target != null && out.what() != null && out.what().equals(target)) {
|
||||
targetOutIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetOutIndex == -1) {
|
||||
for (int i = 0; i < baseOutputs.length; i++) {
|
||||
if (baseOutputs[i] != null) {
|
||||
targetOutIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetOutIndex == -1 && baseOutputs.length > 0) targetOutIndex = 0;
|
||||
|
||||
long perOperationTarget = 1L;
|
||||
if (targetOutIndex >= 0 && baseOutputs[targetOutIndex] != null) {
|
||||
long amt = baseOutputs[targetOutIndex].amount();
|
||||
if (amt > 0) perOperationTarget = amt;
|
||||
}
|
||||
|
||||
// 使用最小整数倍(ceil)策略:直接选择满足请求的最小倍数
|
||||
long multiplier = 1L;
|
||||
if (requestedAmount > 0) {
|
||||
long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1);
|
||||
multiplier = needed <= 1L ? 1L : needed;
|
||||
}
|
||||
|
||||
// 构建压缩输入(将每个输入的 multiplier 翻倍,保留每个模板的原始数量)
|
||||
IInput[] scaledInputs = new IInput[baseInputs.length];
|
||||
for (int i = 0; i < baseInputs.length; i++) {
|
||||
var in = baseInputs[i];
|
||||
var template = in.getPossibleInputs();
|
||||
GenericStack[] scaledTemplates = new GenericStack[template.length];
|
||||
for (int j = 0; j < template.length; j++) {
|
||||
scaledTemplates[j] = new GenericStack(template[j].what(), template[j].amount());
|
||||
}
|
||||
scaledInputs[i] = new ScaledProcessingPattern.Input(scaledTemplates, in.getMultiplier() * multiplier);
|
||||
}
|
||||
|
||||
/* 4. 构建压缩输出 */
|
||||
GenericStack[] scaledCondensedOutputs = new GenericStack[baseOutputs.length];
|
||||
for (int i = 0; i < baseOutputs.length; i++) {
|
||||
GenericStack out = baseOutputs[i];
|
||||
if (out != null) {
|
||||
scaledCondensedOutputs[i] = new GenericStack(out.what(), out.amount() * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建并打印稀疏表示(直接按 multiplier 放大)
|
||||
GenericStack[] scaledSparseInputs = new GenericStack[baseSparseInputs.length];
|
||||
for (int i = 0; i < baseSparseInputs.length; i++) {
|
||||
var in = baseSparseInputs[i];
|
||||
if (in != null) {
|
||||
scaledSparseInputs[i] = new GenericStack(in.what(), in.amount() * multiplier);
|
||||
}
|
||||
}
|
||||
GenericStack[] scaledSparseOutputs = new GenericStack[baseSparseOutputs.length];
|
||||
for (int i = 0; i < baseSparseOutputs.length; i++) {
|
||||
var out = baseSparseOutputs[i];
|
||||
if (out != null) {
|
||||
scaledSparseOutputs[i] = new GenericStack(out.what(), out.amount() * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
return new ScaledProcessingPattern(base,
|
||||
base.getDefinition(),
|
||||
scaledSparseInputs,
|
||||
scaledSparseOutputs,
|
||||
scaledInputs,
|
||||
scaledCondensedOutputs);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.extendedae_plus.util;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Thread-local stack holder for requested amounts to support nested requests.
|
||||
*/
|
||||
public final class RequestedAmountHolder {
|
||||
private static final ThreadLocal<Deque<Long>> HOLDER = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
private RequestedAmountHolder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a requested amount onto the thread-local stack.
|
||||
*/
|
||||
public static void push(long v) {
|
||||
Deque<Long> dq = HOLDER.get();
|
||||
dq.push(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop the top value from the thread-local stack. Safe if empty.
|
||||
*/
|
||||
public static void pop() {
|
||||
Deque<Long> dq = HOLDER.get();
|
||||
if (dq.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dq.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the current requested amount or return 0 if none.
|
||||
*/
|
||||
public static long get() {
|
||||
Deque<Long> dq = HOLDER.get();
|
||||
Long v = dq.peek();
|
||||
return v == null ? 0L : v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"variants": {
|
||||
"": { "model": "extendedae_plus:block/network_pattern_controller" }
|
||||
}
|
||||
}
|
||||
|
|
@ -14,5 +14,25 @@
|
|||
"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"
|
||||
"itemGroup.extendedae_plus.main": "ExtendedAE Plus",
|
||||
|
||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "Wireless Transceiver Info",
|
||||
"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 Position",
|
||||
"config.jade.plugin_extendedae_plus.wt_locked": "Show Locked State",
|
||||
"config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State",
|
||||
"extendedae_plus.tooltip.frequency": "Frequency: %d",
|
||||
"extendedae_plus.tooltip.master_mode": "Mode: %s",
|
||||
"extendedae_plus.tooltip.locked": "Locked: %s"
|
||||
,
|
||||
"screen.extendedae_plus.title": "ExtendedAE Plus Config",
|
||||
"config.extendedae_plus.pageMultiplier": "Pattern Provider Page Multiplier",
|
||||
"config.extendedae_plus.pageMultiplier_with_range": "Pattern Provider Page Multiplier (1-64)",
|
||||
"config.extendedae_plus.wirelessMaxRange": "Wireless Max Range",
|
||||
"config.extendedae_plus.wirelessMaxRange_with_range": "Wireless Max Range (1-4096)",
|
||||
"config.extendedae_plus.wirelessCrossDimEnable": "Allow Wireless Cross-Dimension",
|
||||
"config.extendedae_plus.providerRoundRobinEnable": "Enable Provider Round-Robin (Smart Doubling)",
|
||||
"config.extendedae_plus.state_on": "On",
|
||||
"config.extendedae_plus.state_off": "Off"
|
||||
}
|
||||
|
|
@ -27,5 +27,32 @@
|
|||
"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.1024x_crafting_accelerator": "1024x并行处理单元",
|
||||
|
||||
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
|
||||
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",
|
||||
"config.jade.plugin_extendedae_plus.wt_master_mode": "显示主/从模式",
|
||||
"config.jade.plugin_extendedae_plus.wt_master_location": "显示主节点位置",
|
||||
"config.jade.plugin_extendedae_plus.wt_locked": "显示锁定状态",
|
||||
"config.jade.plugin_extendedae_plus.wt_network_usable": "显示网络在线状态",
|
||||
"extendedae_plus.tooltip.frequency": "频率: %d",
|
||||
"extendedae_plus.tooltip.master_mode": "模式: %s",
|
||||
"extendedae_plus.tooltip.locked": "锁定状态: %s",
|
||||
|
||||
"screen.extendedae_plus.title": "ExtendedAE Plus 配置",
|
||||
"config.extendedae_plus.pageMultiplier": "扩展样板供应器槽位倍率",
|
||||
"config.extendedae_plus.pageMultiplier_with_range": "扩展样板供应器槽位倍率 (1-64)",
|
||||
"config.extendedae_plus.wirelessMaxRange": "无线最大距离",
|
||||
"config.extendedae_plus.wirelessMaxRange_with_range": "无线最大距离 (1-4096)",
|
||||
"config.extendedae_plus.wirelessCrossDimEnable": "无线收发器允许跨维度连接",
|
||||
"config.extendedae_plus.providerRoundRobinEnable": "启用样板供应器轮询分配(智能翻倍)",
|
||||
"config.extendedae_plus.state_on": "开",
|
||||
"config.extendedae_plus.state_off": "关",
|
||||
"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/network_pattern_controller"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "extendedae_plus:block/network_pattern_controller"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"rolls": 1,
|
||||
"entries": [
|
||||
{ "type": "minecraft:item", "name": "extendedae_plus:network_pattern_controller" }
|
||||
],
|
||||
"conditions": [
|
||||
{ "condition": "minecraft:survives_explosion" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"ingredients": [
|
||||
{ "item": "ae2:semi_dark_monitor" },
|
||||
{ "item": "ae2:pattern_provider" },
|
||||
{ "item": "ae2:network_tool" }
|
||||
],
|
||||
"result": {
|
||||
"item": "extendedae_plus:network_pattern_controller",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"extendedae_plus:network_pattern_controller",
|
||||
"extendedae_plus:wireless_transceiver",
|
||||
"extendedae_plus:4x_crafting_accelerator",
|
||||
"extendedae_plus:16x_crafting_accelerator",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
"PickFromWirelessMixin",
|
||||
"accessor.AbstractContainerScreenAccessor",
|
||||
"accessor.ScreenAccessor",
|
||||
"accessor.ScreenInvoker",
|
||||
"ae2.AEBaseScreenMixin",
|
||||
"ae2.EncodedPatternItemMixin",
|
||||
"ae2.PatternEncodingTermScreenMixin",
|
||||
|
|
@ -24,25 +23,38 @@
|
|||
"extendedae.HighlightButtonMixin",
|
||||
"extendedae.accessor.GuiExPatternTerminalAccessor",
|
||||
"extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor",
|
||||
"jei.EncodePatternTransferHandlerMixin",
|
||||
"hooks.ModelBakeryMixin"
|
||||
"ae2.EncodePatternTransferHandlerMixin",
|
||||
"ae2.EncodingHelperMixin",
|
||||
"jei.accessor.BookmarkOverlayAccessor",
|
||||
"hooks.ModelBakeryMixin",
|
||||
"jei.EncodePatternTransferHandlerMixin"
|
||||
],
|
||||
"mixins": [
|
||||
"ae2.AEProcessingPatternMixin",
|
||||
"ae2.ContainerPatternEncodingTermMenuMixin",
|
||||
"ae2.CraftingCPUClusterMixin",
|
||||
"ae2.MEStorageMenuMixin",
|
||||
"ae2.PatternEncodingTermMenuMixin",
|
||||
"ae2.PatternProviderLogicAdvancedMixin",
|
||||
"ae2.PatternProviderLogicDoublingMixin",
|
||||
"ae2.PatternProviderMenuAdvancedMixin",
|
||||
"ae2.PatternProviderMenuDoublingMixin",
|
||||
"ae2.accessor.MEStorageMenuAccessor",
|
||||
"ae2.accessor.PatternEncodingTermMenuAccessor",
|
||||
"ae2.accessor.PatternProviderLogicAccessor",
|
||||
"ae2.accessor.PatternProviderLogicPatternInputsAccessor",
|
||||
"ae2.accessor.PatternProviderLogicPatternsAccessor",
|
||||
"ae2.accessor.PatternProviderMenuAdvancedAccessor",
|
||||
"ae2WTlib.ContainerUWirelessExPatternTerminalMixin",
|
||||
"autopattern.CraftingProviderListAccessor",
|
||||
"autopattern.CraftingServiceGetProvidersMixin",
|
||||
"autopattern.CraftingTreeNodeAccessor",
|
||||
"autopattern.CraftingTreeNodeMixin",
|
||||
"autopattern.CraftingTreeProcessMixin",
|
||||
"autopattern.PatternProviderLogicContainsRedirectMixin",
|
||||
"extendedae.ContainerExPatternProviderMixin",
|
||||
"extendedae.ContainerExPatternTerminalMixin",
|
||||
"extendedae.ContainerWirelessExPatternTerminalMixin",
|
||||
"ae2.CraftingCPUClusterMixin",
|
||||
"extendedae.PartExPatternProviderMixin",
|
||||
"extendedae.TileExPatternProviderMixin"
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user