扩展了搜索的功能, 给配方界面也加了快速上传(‾◡◝)

This commit is contained in:
XianYuFish001 2025-09-26 13:19:06 +08:00
parent cc1ec9fa16
commit 7eb9669180
20 changed files with 354 additions and 236 deletions

View File

@ -15,7 +15,7 @@ loader_version_range=[1,)
mod_id=extendedae_plus mod_id=extendedae_plus
mod_name=ExtendedAE-Plus mod_name=ExtendedAE-Plus
mod_license=LGPL-3.0-or-later mod_license=LGPL-3.0-or-later
mod_version=1.0.1 mod_version=1.0.2
mod_group_id=com.extendedae_plus mod_group_id=com.extendedae_plus
mod_authors=GaLicn, XianYuFish001 mod_authors=GaLicn, XianYuFish001
mod_description= mod_description=

View File

@ -1,6 +1,17 @@
# ExtendedAE Plus # ExtendedAE Plus
_这个分支是粗浅的EMI适配,技术力不足,请见谅_ _这个分支是粗浅的EMI适配,技术力不足,请见谅_
### 相较于主分支(1.4.0)的改动: ### 相较于主分支(1.4.0)的改动
- 删除了JEI的适配 - 删除了JEI的适配
- 一些QoL功能, 针对EMI环境下遇到的问题
### 增加/更改的QoL功能
- 在EMI配方/配方树界面快速拉起样板编写&上传
- 扩展了样板供应器选择界面的搜索功能
- 现在可以匹配样板供应器目标方块的本地化键名
- 允许在多个获取到的关键词间切换
- 默认顺序:
- | 别名 _(不包含特殊字符的字符串现在都会被视为别名)_
- | EMI配方类别本地化关键词
- | 原版配方关键词
- ↓ EMI配方关键词
- 更多功能正在极速构思中 _(您知道吗? 这里的构思与构思谐音, 令人忍俊不禁)_

View File

@ -3,6 +3,7 @@ package com.extendedae_plus.client;
import appeng.api.stacks.GenericStack; import appeng.api.stacks.GenericStack;
import appeng.client.gui.me.common.MEStorageScreen; import appeng.client.gui.me.common.MEStorageScreen;
import appeng.core.AEConfig; import appeng.core.AEConfig;
import appeng.integration.modules.emi.EmiStackHelper;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor; import com.extendedae_plus.mixin.ae2.accessor.MEStorageScreenAccessor;
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor; import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor;
@ -14,15 +15,11 @@ import dev.emi.emi.api.EmiApi;
import dev.emi.emi.api.stack.EmiStack; import dev.emi.emi.api.stack.EmiStack;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.material.Fluid;
import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.InputEvent; import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.client.event.ScreenEvent; import net.neoforged.neoforge.client.event.ScreenEvent;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@ -32,17 +29,6 @@ import java.util.List;
public final class InputEvents { public final class InputEvents {
private InputEvents() {} private InputEvents() {}
private static GenericStack toGenericStack(EmiStack stack) {
try {
GenericStack genericStack = null;
Object typed = stack.getKey();
if (typed instanceof Item item) genericStack = GenericStack.fromItemStack(new ItemStack(item));
else if (typed instanceof Fluid fluid) genericStack = GenericStack.fromFluidStack(new FluidStack(fluid, 1000));
return genericStack;
} catch (Throwable ignored) {}
return null;
}
@SubscribeEvent @SubscribeEvent
public static void onMouseButtonPre(InputEvent.MouseButton.Pre event) { public static void onMouseButtonPre(InputEvent.MouseButton.Pre event) {
if (event.getAction() != GLFW.GLFW_PRESS) return; if (event.getAction() != GLFW.GLFW_PRESS) return;
@ -55,7 +41,7 @@ public final class InputEvents {
// Optional<ITypedIngredient<?>> hovered = new JemiBookmarkOverlay().getIngredientUnderMouse(); // Optional<ITypedIngredient<?>> hovered = new JemiBookmarkOverlay().getIngredientUnderMouse();
List<EmiStack> stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks(); List<EmiStack> stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks();
if (stacks.isEmpty()) return; if (stacks.isEmpty()) return;
GenericStack stack = toGenericStack(stacks.getFirst()); GenericStack stack = EmiStackHelper.toGenericStack(stacks.getFirst());
if (stack == null) return; if (stack == null) return;
PacketDistributor.sendToServer(new PullFromJeiOrCraftC2SPacket(stack)); PacketDistributor.sendToServer(new PullFromJeiOrCraftC2SPacket(stack));
} }
@ -66,7 +52,7 @@ public final class InputEvents {
List<EmiStack> stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks(); List<EmiStack> stacks = EmiApi.getHoveredStack(false).getStack().getEmiStacks();
if (stacks.isEmpty()) return; if (stacks.isEmpty()) return;
GenericStack stack = toGenericStack(stacks.getFirst()); GenericStack stack = EmiStackHelper.toGenericStack(stacks.getFirst());
if (stack == null) return; if (stack == null) return;
// 发送到服务端让其验证并打开 CraftAmountMenu // 发送到服务端让其验证并打开 CraftAmountMenu
@ -112,13 +98,13 @@ public final class InputEvents {
} catch (Throwable ignored) { } catch (Throwable ignored) {
} }
} }
} else if (event.getKeyCode() == GLFW.GLFW_KEY_LEFT_SHIFT) } else if (event.getKeyCode() == GLFW.GLFW_KEY_LEFT_CONTROL)
PacketDistributor.sendToServer(new C2SPacketTargetKeyTriggered(C2SPacketTargetKeyTriggered.KeyType.SHIFT_DOWN)); PacketDistributor.sendToServer(new C2SPacketTargetKeyTriggered(C2SPacketTargetKeyTriggered.KeyType.CTRL_DOWN));
} }
@SubscribeEvent @SubscribeEvent
public static void onKeyReleasePre(ScreenEvent.KeyReleased.Pre event) { public static void onKeyReleasePre(ScreenEvent.KeyReleased.Pre event) {
if (event.getKeyCode() == GLFW.GLFW_KEY_LEFT_SHIFT) if (event.getKeyCode() == GLFW.GLFW_KEY_LEFT_CONTROL)
PacketDistributor.sendToServer(new C2SPacketTargetKeyTriggered(C2SPacketTargetKeyTriggered.KeyType.SHIFT_UP)); PacketDistributor.sendToServer(new C2SPacketTargetKeyTriggered(C2SPacketTargetKeyTriggered.KeyType.CTRL_UP));
} }
} }

View File

@ -1,5 +1,5 @@
package com.extendedae_plus.client; package com.extendedae_plus.client;
public interface PatternEncodingTermMenuMixinHelper { public interface PatternEncodingTermMenuMixinHelper {
void eaep$setShiftPressed(boolean pressed); void eaep$setCtrlPressed(boolean pressed);
} }

View File

@ -1,11 +1,15 @@
package com.extendedae_plus.client.ui; package com.extendedae_plus.client.ui;
import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket; import com.extendedae_plus.network.UploadEncodedPatternToProviderC2SPacket;
import com.extendedae_plus.util.ExtendedAEPatternUploadUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import java.util.*; import java.util.*;
@ -18,11 +22,13 @@ public class ProviderSelectScreen extends Screen {
// 原始数据 // 原始数据
private final List<Long> ids; private final List<Long> ids;
private final List<String> names; private final List<String> names;
private final List<String> i18nKeys;
private final List<Integer> emptySlots; private final List<Integer> emptySlots;
// 分组后的数据同名合并 // 分组后的数据同名合并
private final List<Long> gIds = new ArrayList<>(); // 代表条目使用的 providerId选择空位数最多的那个 private final List<Long> gIds = new ArrayList<>(); // 代表条目使用的 providerId选择空位数最多的那个
private final List<String> gNames = new ArrayList<>(); // 分组名供应器名称 private final List<String> gNames = new ArrayList<>(); // 分组名供应器名称
private final List<String> gI18nKeys = new ArrayList<>(); // 分组名供应器Key
private final List<Integer> gTotalSlots = new ArrayList<>(); // 该名称下供应器空位总和 private final List<Integer> gTotalSlots = new ArrayList<>(); // 该名称下供应器空位总和
private final List<Integer> gCount = new ArrayList<>(); // 该名称下供应器数量 private final List<Integer> gCount = new ArrayList<>(); // 该名称下供应器数量
@ -35,8 +41,9 @@ public class ProviderSelectScreen extends Screen {
// 搜索框 // 搜索框
private EditBox searchBox; private EditBox searchBox;
// 中文名输入框用于添加映射 // 中文名输入框用于添加映射
private EditBox cnInput; private EditBox aliasInput;
private String query = ""; private String selectedQuery = "";
private List<String> query = new ArrayList<>();
private boolean needsRefresh = false; private boolean needsRefresh = false;
private int page = 0; private int page = 0;
@ -44,19 +51,26 @@ public class ProviderSelectScreen extends Screen {
private final List<Button> entryButtons = new ArrayList<>(); private final List<Button> entryButtons = new ArrayList<>();
public ProviderSelectScreen(Screen parent, List<Long> ids, List<String> names, List<Integer> emptySlots) { public ProviderSelectScreen(Screen parent, List<Long> ids, List<String> names, List<String> i18nKeys, List<Integer> emptySlots) {
super(Component.translatable("extendedae_plus.screen.choose_provider.title")); super(Component.translatable("extendedae_plus.screen.choose_provider.title"));
this.parent = parent; this.parent = parent;
this.ids = ids; this.ids = ids;
this.names = names; this.names = names;
this.i18nKeys = i18nKeys;
this.emptySlots = emptySlots; this.emptySlots = emptySlots;
// 如果有来自 JEI 的最近处理名称则作为初始查询 // 如果有来自 JEI 的最近处理名称则作为初始查询
try { try {
String recent = com.extendedae_plus.util.ExtendedAEPatternUploadUtil.lastProcessingName; List<String> recent = ExtendedAEPatternUploadUtil.lastProcessingNameList;
if (recent != null && !recent.isBlank()) { if (recent != null && !recent.isEmpty()) {
this.query = recent; this.query = new ArrayList<>();
recent.forEach(q -> {
String alias = ExtendedAEPatternUploadUtil.findMapping(q);
if (alias != null) query.addFirst(alias);
else query.addLast(q);
});
this.selectedQuery = query.getFirst();
// 用后即清空避免污染下次 // 用后即清空避免污染下次
com.extendedae_plus.util.ExtendedAEPatternUploadUtil.lastProcessingName = null; ExtendedAEPatternUploadUtil.lastProcessingNameList.clear();
} }
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
buildGroups(); buildGroups();
@ -80,12 +94,28 @@ public class ProviderSelectScreen extends Screen {
searchBox.setY(startY - 25); searchBox.setY(startY - 25);
searchBox.setWidth(240); searchBox.setWidth(240);
} }
searchBox.setValue(query); searchBox.setValue(selectedQuery);
// 为多个候选的情况添加Tooltip指示
if (query.size() > 1) {
MutableComponent candidateQuery = Component.literal("候选关键词");
query.forEach(q -> {
if (selectedQuery.equalsIgnoreCase(q)) candidateQuery
.append(Component.literal("\n> ").withStyle(ChatFormatting.GREEN))
.append(Component.literal(q).withStyle(ChatFormatting.BOLD));
else candidateQuery.append("\n" +q);
});
searchBox.setTooltip(Tooltip.create(candidateQuery));
}
searchBox.setResponder(text -> { searchBox.setResponder(text -> {
// 只有当输入真正发生变化时才重置页码与过滤 // 只有当输入真正发生变化时才重置页码与过滤
if (Objects.equals(text, query)) return; if (Objects.equals(text, selectedQuery)) return;
query = text; selectedQuery = text;
// 切换候选词不触发, 手动输入时重置query
query.clear();
page = 0; page = 0;
searchBox.setTooltip(Tooltip.create(Component.empty()));
applyFilter(); applyFilter();
// 避免在回调中直接重建 UI延迟到下一次 tick // 避免在回调中直接重建 UI延迟到下一次 tick
needsRefresh = true; needsRefresh = true;
@ -129,14 +159,14 @@ public class ProviderSelectScreen extends Screen {
this.addRenderableWidget(reload); this.addRenderableWidget(reload);
// 中文名输入框用于新增映射的值 // 中文名输入框用于新增映射的值
if (cnInput == null) { if (aliasInput == null) {
cnInput = new EditBox(this.font, centerX + 50, navY + 30, 120, 20, Component.translatable("extendedae_plus.screen.cn_name")); aliasInput = new EditBox(this.font, centerX + 50, navY + 30, 120, 20, Component.translatable("extendedae_plus.screen.cn_name"));
} else { } else {
cnInput.setX(centerX + 50); aliasInput.setX(centerX + 50);
cnInput.setY(navY + 30); aliasInput.setY(navY + 30);
cnInput.setWidth(120); aliasInput.setWidth(120);
} }
this.addRenderableWidget(cnInput); this.addRenderableWidget(aliasInput);
// 增加映射按钮使用当前搜索关键字 -> 中文 // 增加映射按钮使用当前搜索关键字 -> 中文
Button addMap = Button.builder(Component.translatable("extendedae_plus.screen.add_mapping"), b -> addMappingFromUI()) Button addMap = Button.builder(Component.translatable("extendedae_plus.screen.add_mapping"), b -> addMappingFromUI())
@ -212,12 +242,15 @@ public class ProviderSelectScreen extends Screen {
private void buildGroups() { private void buildGroups() {
// 使用 LinkedHashMap 保持首次出现顺序 // 使用 LinkedHashMap 保持首次出现顺序
Map<String, Group> map = new LinkedHashMap<>(); Map<String, Group> nameMap = new LinkedHashMap<>();
for (int i = 0; i < names.size(); i++) { for (int i = 0; i < names.size(); i++) {
String name = names.get(i); String name = names.get(i);
String i18nKey = i18nKeys.get(i);
long id = ids.get(i); long id = ids.get(i);
int slots = emptySlots.get(i); int slots = emptySlots.get(i);
Group g = map.computeIfAbsent(name, k -> new Group()); Group g = nameMap.computeIfAbsent(name, k -> new Group());
// if (!i18nKey.isBlank() && !keyMap.containsKey(i18nKey))
// keyMap.put(i18nKey, name);
g.count++; g.count++;
g.totalSlots += Math.max(0, slots); g.totalSlots += Math.max(0, slots);
// 挑选空位最多的作为代表 id若并列保留先到者 // 挑选空位最多的作为代表 id若并列保留先到者
@ -225,11 +258,13 @@ public class ProviderSelectScreen extends Screen {
g.bestSlots = slots; g.bestSlots = slots;
g.bestId = id; g.bestId = id;
} }
if (!i18nKey.isBlank()) g.i18nKey = i18nKey;
} }
for (Map.Entry<String, Group> e : map.entrySet()) { for (Map.Entry<String, Group> e : nameMap.entrySet()) {
String name = e.getKey(); String name = e.getKey();
Group g = e.getValue(); Group g = e.getValue();
gNames.add(name); gNames.add(name);
gI18nKeys.add(g.i18nKey);
gIds.add(g.bestId); gIds.add(g.bestId);
gTotalSlots.add(g.totalSlots); gTotalSlots.add(g.totalSlots);
gCount.add(g.count); gCount.add(g.count);
@ -241,6 +276,7 @@ public class ProviderSelectScreen extends Screen {
int bestSlots = Integer.MIN_VALUE; int bestSlots = Integer.MIN_VALUE;
int totalSlots = 0; int totalSlots = 0;
int count = 0; int count = 0;
String i18nKey = "";
} }
private void applyFilter() { private void applyFilter() {
@ -248,10 +284,11 @@ public class ProviderSelectScreen extends Screen {
fNames.clear(); fNames.clear();
fTotalSlots.clear(); fTotalSlots.clear();
fCount.clear(); fCount.clear();
String q = query == null ? "" : query.trim(); String q = query == null ? "" : selectedQuery.trim();
for (int i = 0; i < gIds.size(); i++) { for (int i = 0; i < gIds.size(); i++) {
String name = gNames.get(i); String name = gNames.get(i);
if (q.isEmpty() || nameMatches(name, q)) { String i18nKey = gI18nKeys.get(i);
if (q.isEmpty() || i18nKeyMatches(i18nKey, q) || nameMatches(name, q)) {
fIds.add(gIds.get(i)); fIds.add(gIds.get(i));
fNames.add(name); fNames.add(name);
fTotalSlots.add(gTotalSlots.get(i)); fTotalSlots.add(gTotalSlots.get(i));
@ -278,7 +315,7 @@ public class ProviderSelectScreen extends Screen {
JEC_AVAILABLE = false; JEC_AVAILABLE = false;
} }
} }
if (Boolean.TRUE.equals(JEC_AVAILABLE) && JEC_CONTAINS != null) { if (JEC_AVAILABLE && JEC_CONTAINS != null) {
Object r = JEC_CONTAINS.invoke(null, name, key); Object r = JEC_CONTAINS.invoke(null, name, key);
if (r instanceof Boolean && (Boolean) r) return true; if (r instanceof Boolean && (Boolean) r) return true;
// 再尝试大小写不敏感双方转为小写重新匹配 // 再尝试大小写不敏感双方转为小写重新匹配
@ -294,6 +331,12 @@ public class ProviderSelectScreen extends Screen {
return name.toLowerCase().contains(key.toLowerCase()); return name.toLowerCase().contains(key.toLowerCase());
} }
private static boolean i18nKeyMatches(String i18nKey, String key) {
if (i18nKey == null || i18nKey.isBlank()) return false;
if (key == null || key.isEmpty()) return true;
return i18nKey.toLowerCase().contains(key.toLowerCase());
}
@Override @Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) { public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (searchBox != null && searchBox.keyPressed(keyCode, scanCode, modifiers)) { if (searchBox != null && searchBox.keyPressed(keyCode, scanCode, modifiers)) {
@ -322,8 +365,10 @@ public class ProviderSelectScreen extends Screen {
if (!this.searchBox.getValue().isEmpty()) { if (!this.searchBox.getValue().isEmpty()) {
this.searchBox.setValue(""); this.searchBox.setValue("");
} }
this.query = ""; this.selectedQuery = "";
this.query.clear();
this.page = 0; this.page = 0;
searchBox.setTooltip(Tooltip.create(Component.empty()));
applyFilter(); applyFilter();
this.needsRefresh = true; this.needsRefresh = true;
return true; return true;
@ -332,6 +377,37 @@ public class ProviderSelectScreen extends Screen {
return super.mouseClicked(mouseX, mouseY, button); return super.mouseClicked(mouseX, mouseY, button);
} }
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
if (query.size() <= 1 || this.searchBox == null)
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
boolean direction = scrollY > 0;
int boxX = this.searchBox.getX();
int boxY = this.searchBox.getY();
int boxWidth = this.searchBox.getWidth();
int boxHeight = this.searchBox.getHeight();
// 滚轮切换候选词
if (mouseX >= boxX && mouseX <= boxX + boxWidth &&
mouseY >= boxY && mouseY <= boxY + boxHeight &&
query.contains(selectedQuery)) {
int index = query.indexOf(selectedQuery);
if (direction && index == 0) selectedQuery = query.getLast();
else if (direction) selectedQuery = query.get(index - 1);
else if (index == query.size() - 1) selectedQuery = query.getFirst();
else selectedQuery = query.get(index + 1);
this.page = 0;
applyFilter();
this.needsRefresh = true;
return true;
}
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
@Override @Override
public void tick() { public void tick() {
super.tick(); super.tick();
@ -343,25 +419,26 @@ public class ProviderSelectScreen extends Screen {
} }
private void addMappingFromUI() { private void addMappingFromUI() {
String key = query == null ? "" : query.trim(); String key = query == null ? "" : selectedQuery.trim();
String val = cnInput == null ? "" : cnInput.getValue().trim(); String val = aliasInput == null ? "" : aliasInput.getValue().trim();
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
if (key.isEmpty()) { if (key.isEmpty()) {
if (player != null) player.sendSystemMessage(Component.literal("请输入搜索关键字后再添加映射")); if (player != null) player.sendSystemMessage(Component.literal("请输入搜索关键字后再添加映射"));
return; return;
} }
if (val.isEmpty()) { if (val.isEmpty()) {
if (player != null) player.sendSystemMessage(Component.literal("请输入中文名称")); if (player != null) player.sendSystemMessage(Component.literal("请输入待映射别名"));
return; return;
} }
boolean ok = com.extendedae_plus.util.ExtendedAEPatternUploadUtil.addOrUpdateAliasMapping(key, val); boolean ok = ExtendedAEPatternUploadUtil.addOrUpdateAliasMapping(key, val);
if (ok) { if (ok) {
if (player != null) player.sendSystemMessage(Component.literal("已添加/更新映射: " + key + " -> " + val)); if (player != null) player.sendSystemMessage(Component.literal("已添加/更新映射: " + key + " -> " + val));
// 将刚添加的中文名写入搜索框作为当前查询 // 将刚添加的中文名写入搜索框作为当前查询
this.query = val; this.query.remove(selectedQuery);
if (this.searchBox != null) { this.query.addFirst(val);
this.selectedQuery = val;
if (this.searchBox != null)
this.searchBox.setValue(val); this.searchBox.setValue(val);
}
// 更新本地过滤显示若名称包含中文可被搜索 // 更新本地过滤显示若名称包含中文可被搜索
applyFilter(); applyFilter();
// 回到第一页以展示最新筛选结果 // 回到第一页以展示最新筛选结果
@ -374,19 +451,19 @@ public class ProviderSelectScreen extends Screen {
// 使用中文值精确匹配删除映射 // 使用中文值精确匹配删除映射
private void deleteMappingByCnFromUI() { private void deleteMappingByCnFromUI() {
String val = cnInput == null ? "" : cnInput.getValue().trim(); String val = aliasInput == null ? "" : aliasInput.getValue().trim();
var player = Minecraft.getInstance().player; var player = Minecraft.getInstance().player;
if (val.isEmpty()) { if (val.isEmpty()) {
if (player != null) player.sendSystemMessage(Component.literal("请输入中文名称后再删除映射")); if (player != null) player.sendSystemMessage(Component.literal("请输入存在别名后再删除映射"));
return; return;
} }
int removed = com.extendedae_plus.util.ExtendedAEPatternUploadUtil.removeMappingsByCnValue(val); int removed = com.extendedae_plus.util.ExtendedAEPatternUploadUtil.removeMappingsByCnValue(val);
if (removed > 0) { if (removed > 0) {
if (player != null) player.sendSystemMessage(Component.literal("已删除 " + removed + " 条映射,中文= " + val)); if (player != null) player.sendSystemMessage(Component.literal("已删除 " + removed + " 条映射,别名: [" + val + "]"));
applyFilter(); applyFilter();
needsRefresh = true; needsRefresh = true;
} else { } else {
if (player != null) player.sendSystemMessage(Component.literal("未找到中文为 '" + val + "' 的映射")); if (player != null) player.sendSystemMessage(Component.literal("未找到别名为 '" + val + "' 的映射"));
} }
} }
} }

View File

@ -14,13 +14,17 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
public class HandlerBoMRecipes { public class HandlerBoMRecipes {
public static HashMap<ResourceLocation, GenericStack> collectInputs(MaterialNode parentNode) { public static HashMap<ResourceLocation, GenericStack> collectInputs(MaterialNode parentNode, long batches) {
var recipe = parentNode.recipe; var recipe = parentNode.recipe;
if (recipe == null) return new HashMap<>();
if (parentNode.state != FoldState.EXPANDED) return new HashMap<>(); if (parentNode.state != FoldState.EXPANDED) return new HashMap<>();
if (parentNode.children == null) return new HashMap<>(); if (parentNode.children == null) return new HashMap<>();
HashMap<ResourceLocation, GenericStack> results = new HashMap<>(); HashMap<ResourceLocation, GenericStack> results = new HashMap<>();
var type = recipe.getBackingRecipe().value().getType(); RecipeType<?> type;
if (recipe.getBackingRecipe() != null)
type = recipe.getBackingRecipe().value().getType();
else type = null;
parentNode.children.forEach(child -> { parentNode.children.forEach(child -> {
EmiStack stack; EmiStack stack;
@ -31,8 +35,8 @@ public class HandlerBoMRecipes {
recipe.getInputs().forEach(input -> { recipe.getInputs().forEach(input -> {
if (input.getEmiStacks().contains(stack)) { if (input.getEmiStacks().contains(stack)) {
stack.setAmount(input.getEmiStacks().getFirst().getAmount()); EmiStack batchedStack = batchAmount(stack, batches);
GenericStack genericStack = EmiStackHelper.toGenericStack(stack); GenericStack genericStack = EmiStackHelper.toGenericStack(batchedStack);
if (genericStack == null) return; if (genericStack == null) return;
results.put(input.getEmiStacks().getFirst().getId(), genericStack); results.put(input.getEmiStacks().getFirst().getId(), genericStack);
@ -42,42 +46,7 @@ public class HandlerBoMRecipes {
return results; return results;
} }
// public static void handle(C2SPacketStoneCuttingID packet, ServerPlayer player) { public static List<List<GenericStack>> updateRecipe(EmiRecipe original,
// if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
//// RecipeHolder<?> recipeHolder = player.level().getRecipeManager()
//// .byKey(packet.recipeID()).orElse(null);
// EmiRecipe recipe = EmiApi.getRecipeManager()
// .getRecipe(packet.recipeID());
//
// if (recipe == null) return;
//
// try {
// menu.clear();
// List<List<GenericStack>> modifiedInputs = updateRecipe(recipe, packet.batches(), packet.selectedStacks());
//
// // 遍历原配方的每个输入
// for (EmiIngredient originalInput : recipe.getInputs()) {
// if (!originalInput.getEmiStacks().isEmpty()) {
// EmiStack firstStack = originalInput.getEmiStacks().getFirst();
// ResourceLocation stackId = firstStack.getId();
//
// if (packet.selectedStacks().containsKey(stackId))
// modifiedInputs.add(List.of(
// packet.selectedStacks().getOrDefault(stackId, EmiStackHelper.toGenericStack(firstStack))));
// }
// }
//
// // 获取输出保持不变
// List<GenericStack> outputs = EmiStackHelper.ofOutputs(recipe);
//
// // 编码修改后的配方
//// EncodingHelper.encodeProcessingRecipe(menu, modifiedInputs, outputs);
// } catch (Exception e) {
// ExtendedAEPlus.LOGGER.debug("encode failed:", e);
// }
// }
public static List<List<GenericStack>> updateRecipe(EmiRecipe original, long batches,
HashMap<ResourceLocation, GenericStack> selectedInputs) { HashMap<ResourceLocation, GenericStack> selectedInputs) {
List<List<GenericStack>> modifiedInputs = new ArrayList<>(); List<List<GenericStack>> modifiedInputs = new ArrayList<>();
@ -93,4 +62,8 @@ public class HandlerBoMRecipes {
}); });
return modifiedInputs; return modifiedInputs;
} }
public static EmiStack batchAmount(EmiStack original, long batches) {
return original.copy().setAmount(original.getAmount() * batches);
}
} }

View File

@ -2,10 +2,8 @@ package com.extendedae_plus.mixin.ae2.accessor;
import appeng.menu.me.items.PatternEncodingTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot; import appeng.menu.slot.RestrictedInputSlot;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(PatternEncodingTermMenu.class) @Mixin(PatternEncodingTermMenu.class)
public interface PatternEncodingTermMenuAccessor { public interface PatternEncodingTermMenuAccessor {
@ -14,10 +12,4 @@ public interface PatternEncodingTermMenuAccessor {
@Accessor("blankPatternSlot") @Accessor("blankPatternSlot")
RestrictedInputSlot eap$getBlankPatternSlot(); RestrictedInputSlot eap$getBlankPatternSlot();
@Invoker("encodePattern")
ItemStack eaep$encodePattern();
@Invoker("isPattern")
boolean eaep$isPattern(ItemStack output);
} }

View File

@ -0,0 +1,27 @@
package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.client.gui.widgets.ActionButton;
import appeng.core.localization.ButtonToolTips;
import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.util.GetKey;
import net.minecraft.network.chat.Component;
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;
@Mixin(ActionButton.class)
public class MixinActionButton {
// @ModifyVariable(method = "buildMessage", at = @At("STORE"))
// private static String eaep$modifyEncodeButtonTooltips(String value) {
// if ()
// }
@Inject(method = "buildMessage", at = @At("TAIL"), cancellable = true)
private void eaep$modifyButtonTooltips(ButtonToolTips displayName,
ButtonToolTips displayValue,
CallbackInfoReturnable<Component> cir) {
if (displayValue == ButtonToolTips.EncodeDescription && !ModConfig.INDEPENDENT_UPLOADING_BUTTON.getAsBoolean())
cir.setReturnValue(cir.getReturnValue().copy().append(
new GetKey(GetKey.TOOLTIP).addStr("encode_button_description").build()));
}
}

View File

@ -64,7 +64,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO, this.eap$AdvancedBlockingEnabled ? YesNo.YES : YesNo.NO,
(btn, backwards) -> { (btn, backwards) -> {
// 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传 // 不做本地切换点击仅发送自定义C2S显示由@GuiSync回传
// debug removed
var conn = Minecraft.getInstance().getConnection(); var conn = Minecraft.getInstance().getConnection();
if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE); if (conn != null) conn.send(ToggleAdvancedBlockingC2SPacket.INSTANCE);
} }
@ -98,7 +97,6 @@ public abstract class PatternProviderScreenMixin<C extends PatternProviderMenu>
Settings.BLOCKING_MODE, Settings.BLOCKING_MODE,
this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO, this.eap$SmartDoublingEnabled ? YesNo.YES : YesNo.NO,
(btn, backwards) -> { (btn, backwards) -> {
// debug removed
var conn = Minecraft.getInstance().getConnection(); var conn = Minecraft.getInstance().getConnection();
if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE); if (conn != null) conn.send(ToggleSmartDoublingC2SPacket.INSTANCE);
} }

View File

@ -10,7 +10,6 @@ import appeng.core.definitions.AEItems;
import appeng.helpers.IPatternTerminalMenuHost; import appeng.helpers.IPatternTerminalMenuHost;
import appeng.menu.me.items.PatternEncodingTermMenu; import appeng.menu.me.items.PatternEncodingTermMenu;
import appeng.menu.slot.RestrictedInputSlot; import appeng.menu.slot.RestrictedInputSlot;
import appeng.parts.encoding.EncodingMode;
import com.extendedae_plus.client.PatternEncodingTermMenuMixinHelper; import com.extendedae_plus.client.PatternEncodingTermMenuMixinHelper;
import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.config.ModConfig;
import com.extendedae_plus.mixin.ae2.accessor.MEStorageMenuAccessor; import com.extendedae_plus.mixin.ae2.accessor.MEStorageMenuAccessor;
@ -19,6 +18,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.MenuType; import net.minecraft.world.inventory.MenuType;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
@ -31,16 +31,14 @@ public abstract class PatternEncodingTermMenuMixin implements PatternEncodingTer
// 防止重复执行 // 防止重复执行
@Unique @Unique
private boolean eap$blankAutoFilled = false; private boolean eap$blankAutoFilled = false;
@Shadow @Shadow @Final
private RestrictedInputSlot blankPatternSlot; private RestrictedInputSlot blankPatternSlot;
@Shadow
public EncodingMode mode;
@Unique @Unique
public boolean isShiftPressed = false; public boolean eaep$isCtrlPressed = false;
@Unique @Unique
public void eaep$setShiftPressed(boolean press) { public void eaep$setCtrlPressed(boolean press) {
isShiftPressed = press; eaep$isCtrlPressed = press;
} }
@Unique @Unique
@ -165,8 +163,8 @@ public abstract class PatternEncodingTermMenuMixin implements PatternEncodingTer
@Inject(method = "encode", at = @At("TAIL")) @Inject(method = "encode", at = @At("TAIL"))
private void eaep$onEncode(CallbackInfo ci) { private void eaep$onEncode(CallbackInfo ci) {
if (ModConfig.INDEPENDENT_UPLOADING_BUTTON.getAsBoolean()) return; if (ModConfig.INDEPENDENT_UPLOADING_BUTTON.getAsBoolean()) return;
if (!isShiftPressed) return; if (!eaep$isCtrlPressed) return;
isShiftPressed = false; eaep$isCtrlPressed = false;
var self = (PatternEncodingTermMenu) (Object) this; var self = (PatternEncodingTermMenu) (Object) this;
PacketDistributor.sendToPlayer((ServerPlayer) self.getPlayer(), C2SPacketEncodeFinished.INSTANCE); PacketDistributor.sendToPlayer((ServerPlayer) self.getPlayer(), C2SPacketEncodeFinished.INSTANCE);
} }

View File

@ -56,26 +56,36 @@ public abstract class MixinBoMScreen {
if (isCraftingRecipe) { if (isCraftingRecipe) {
PatternFillingHelper.encodeCraftingRecipe(menu, PatternFillingHelper.encodeCraftingRecipe(menu,
node.recipe.getBackingRecipe(), node.recipe.getBackingRecipe(),
HandlerBoMRecipes.updateRecipe(node.recipe, BoM.tree.batches, HandlerBoMRecipes.updateRecipe(node.recipe,
HandlerBoMRecipes.collectInputs(node)), HandlerBoMRecipes.collectInputs(node, 1)),
stack -> true); stack -> true);
menu.encode(); menu.encode();
} else { } else {
EmiStack nodeStack = node.ingredient.getEmiStacks().getFirst();
List<EmiStack> outputs = new ArrayList<>(node.recipe.getOutputs()); List<EmiStack> outputs = new ArrayList<>(node.recipe.getOutputs());
EmiStack nodeStack = outputs.get(outputs.indexOf(node.ingredient.getEmiStacks().getFirst()));
outputs.remove(nodeStack); outputs.remove(nodeStack);
outputs.addFirst(nodeStack); outputs.addFirst(nodeStack);
PatternFillingHelper.encodeProcessingRecipe(menu, PatternFillingHelper.encodeProcessingRecipe(menu,
HandlerBoMRecipes.updateRecipe(node.recipe, BoM.tree.batches, HandlerBoMRecipes.updateRecipe(node.recipe,
HandlerBoMRecipes.collectInputs(node)), HandlerBoMRecipes.collectInputs(node, BoM.tree.batches)),
outputs.stream().map(EmiStackHelper::toGenericStack).toList()); outputs.stream()
.map(stack -> HandlerBoMRecipes.batchAmount(stack, BoM.tree.batches))
.map(EmiStackHelper::toGenericStack).toList());
menu.encode(); menu.encode();
ExtendedAEPatternUploadUtil.addLastProcessingNameList(
node.recipe.getCategory().getName().getString());
if (node.recipe.getBackingRecipe() != null) {
String name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(node.recipe.getBackingRecipe().value()); String name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(node.recipe.getBackingRecipe().value());
if (!(name == null || name.isBlank())) if (!(name == null || name.isBlank()))
ExtendedAEPatternUploadUtil.setLastProcessingName(name); ExtendedAEPatternUploadUtil.addLastProcessingNameList(name);
}
ExtendedAEPatternUploadUtil.addLastProcessingNameList(
node.recipe.getId().toString().split("/")[0]);
PacketDistributor.sendToServer(RequestProvidersListC2SPacket.INSTANCE); PacketDistributor.sendToServer(RequestProvidersListC2SPacket.INSTANCE);
} }

View File

@ -23,8 +23,14 @@ public abstract class MixinEncodePatternTransfer {
// 对不起老牛😭 // 对不起老牛😭
if (recipe == null) return; if (recipe == null) return;
if (EncodingHelper.isSupportedCraftingRecipe(recipe)) return; if (EncodingHelper.isSupportedCraftingRecipe(recipe)) return;
String name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(recipe); String name = ExtendedAEPatternUploadUtil.mapRecipeTypeToSearchKey(recipe);
if (name == null || name.isBlank()) return; if (!(name == null || name.isBlank()))
ExtendedAEPatternUploadUtil.setLastProcessingName(name); ExtendedAEPatternUploadUtil.addLastProcessingNameList(name);
ExtendedAEPatternUploadUtil.addLastProcessingNameList(emiRecipe.getCategory().getName().getString());
if (emiRecipe.getId() != null)
ExtendedAEPatternUploadUtil.addLastProcessingNameList(emiRecipe.getId().toString().split("/")[0]);
} }
} }

View File

@ -0,0 +1,25 @@
package com.extendedae_plus.mixin.emi;
import appeng.menu.me.items.PatternEncodingTermMenu;
import dev.emi.emi.api.widget.RecipeFillButtonWidget;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.world.entity.player.Player;
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;
@Mixin(RecipeFillButtonWidget.class)
public class MixinRecipeFillButton {
@Inject(method = "mouseClicked", at = @At("RETURN"))
private static void eaep$onMouseClicked(int mouseX, int mouseY, int button,
CallbackInfoReturnable<Boolean> cir) {
if (!cir.getReturnValue()) return;
Player player = Minecraft.getInstance().player;
if (player == null) return;
if (!(player.containerMenu instanceof PatternEncodingTermMenu menu)) return;
if (!Screen.hasControlDown()) return;
menu.encode();
}
}

View File

@ -13,8 +13,8 @@ import net.neoforged.neoforge.network.handling.IPayloadContext;
public record C2SPacketTargetKeyTriggered(KeyType keyType) implements CustomPacketPayload{ public record C2SPacketTargetKeyTriggered(KeyType keyType) implements CustomPacketPayload{
public enum KeyType { public enum KeyType {
SHIFT_DOWN, CTRL_DOWN,
SHIFT_UP CTRL_UP
} }
@Override @Override
@ -36,9 +36,9 @@ public record C2SPacketTargetKeyTriggered(KeyType keyType) implements CustomPack
if (player.containerMenu instanceof PatternEncodingTermMenu patternMenu) { if (player.containerMenu instanceof PatternEncodingTermMenu patternMenu) {
if (patternMenu instanceof PatternEncodingTermMenuMixinHelper accessor) { if (patternMenu instanceof PatternEncodingTermMenuMixinHelper accessor) {
accessor.eaep$setShiftPressed(switch (packet.keyType) { accessor.eaep$setCtrlPressed(switch (packet.keyType) {
case SHIFT_UP -> false; case CTRL_UP -> false;
case SHIFT_DOWN -> true; case CTRL_DOWN -> true;
}); });
} }
} }

View File

@ -2,8 +2,10 @@ package com.extendedae_plus.network;
import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.client.ui.ProviderSelectScreen; import com.extendedae_plus.client.ui.ProviderSelectScreen;
import com.mojang.serialization.Codec;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -11,49 +13,24 @@ import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn; import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext; import net.neoforged.neoforge.network.handling.IPayloadContext;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* S2C: 返回可见且有空位的样板供应器列表客户端弹窗展示供用户选择 * S2C: 返回可见且有空位的样板供应器列表客户端弹窗展示供用户选择
*/ */
public class ProvidersListS2CPacket implements CustomPacketPayload { public record ProvidersListS2CPacket(List<Long> ids, List<String> names, List<String> i18nKeys,
List<Integer> emptySlots) implements CustomPacketPayload {
public static final Type<ProvidersListS2CPacket> TYPE = new Type<>( public static final Type<ProvidersListS2CPacket> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "providers_list")); ResourceLocation.fromNamespaceAndPath(ExtendedAEPlus.MODID, "providers_list"));
public static final StreamCodec<RegistryFriendlyByteBuf, ProvidersListS2CPacket> STREAM_CODEC = StreamCodec.of( public static final StreamCodec<RegistryFriendlyByteBuf, ProvidersListS2CPacket> STREAM_CODEC = StreamCodec.composite(
(buf, pkt) -> { ByteBufCodecs.fromCodec(Codec.list(Codec.LONG)), ProvidersListS2CPacket::ids,
buf.writeVarInt(pkt.ids.size()); ByteBufCodecs.fromCodec(Codec.list(Codec.STRING)), ProvidersListS2CPacket::names,
for (int i = 0; i < pkt.ids.size(); i++) { ByteBufCodecs.fromCodec(Codec.list(Codec.STRING)), ProvidersListS2CPacket::i18nKeys,
buf.writeLong(pkt.ids.get(i)); ByteBufCodecs.fromCodec(Codec.list(Codec.INT)), ProvidersListS2CPacket::emptySlots,
buf.writeUtf(pkt.names.get(i)); ProvidersListS2CPacket::new
buf.writeVarInt(pkt.emptySlots.get(i));
}
},
buf -> {
int size = buf.readVarInt();
List<Long> ids = new ArrayList<>(size);
List<String> names = new ArrayList<>(size);
List<Integer> slots = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
ids.add(buf.readLong());
names.add(buf.readUtf());
slots.add(buf.readVarInt());
}
return new ProvidersListS2CPacket(ids, names, slots);
}
); );
private final List<Long> ids;
private final List<String> names;
private final List<Integer> emptySlots;
public ProvidersListS2CPacket(List<Long> ids, List<String> names, List<Integer> emptySlots) {
this.ids = ids;
this.names = names;
this.emptySlots = emptySlots;
}
@Override @Override
public Type<? extends CustomPacketPayload> type() { public Type<? extends CustomPacketPayload> type() {
return TYPE; return TYPE;
@ -66,8 +43,7 @@ public class ProvidersListS2CPacket implements CustomPacketPayload {
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
private static void handleClient(ProvidersListS2CPacket msg) { private static void handleClient(ProvidersListS2CPacket msg) {
var mc = Minecraft.getInstance(); var mc = Minecraft.getInstance();
if (mc == null) return;
var current = mc.screen; var current = mc.screen;
mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.emptySlots)); mc.setScreen(new ProviderSelectScreen(current, msg.ids, msg.names, msg.i18nKeys, msg.emptySlots));
} }
} }

View File

@ -45,6 +45,7 @@ public class RequestProvidersListC2SPacket implements CustomPacketPayload {
List<Long> ids = ExtendedAEPatternUploadUtil.getAllProviderIds(accessMenu); List<Long> ids = ExtendedAEPatternUploadUtil.getAllProviderIds(accessMenu);
List<Long> filteredIds = new ArrayList<>(); List<Long> filteredIds = new ArrayList<>();
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
List<String> i18nKeys = new ArrayList<>();
List<Integer> slots = new ArrayList<>(); List<Integer> slots = new ArrayList<>();
for (Long id : ids) { for (Long id : ids) {
@ -54,17 +55,17 @@ public class RequestProvidersListC2SPacket implements CustomPacketPayload {
if (empty <= 0) continue; // 只列出有空位的 if (empty <= 0) continue; // 只列出有空位的
filteredIds.add(id); filteredIds.add(id);
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu)); names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(id, accessMenu));
i18nKeys.add(ExtendedAEPatternUploadUtil.getProviderI18nName(id, accessMenu));
slots.add(empty); slots.add(empty);
} }
player.connection.send(new ProvidersListS2CPacket(filteredIds, names, slots)); player.connection.send(new ProvidersListS2CPacket(filteredIds, names, i18nKeys, slots));
return; } else {
}
// 回退基于编码终端所在网络枚举供应器负数ID编码索引encodedId = -1 - index // 回退基于编码终端所在网络枚举供应器负数ID编码索引encodedId = -1 - index
List<PatternContainer> containers = ExtendedAEPatternUploadUtil.listAvailableProvidersFromGrid(encMenu); List<PatternContainer> containers = ExtendedAEPatternUploadUtil.listAvailableProvidersFromGrid(encMenu);
List<Long> idxIds = new ArrayList<>(); List<Long> idxIds = new ArrayList<>();
List<String> names = new ArrayList<>(); List<String> names = new ArrayList<>();
List<String> i18nKeys = new ArrayList<>();
List<Integer> slots = new ArrayList<>(); List<Integer> slots = new ArrayList<>();
for (int i = 0; i < containers.size(); i++) { for (int i = 0; i < containers.size(); i++) {
var c = containers.get(i); var c = containers.get(i);
@ -74,9 +75,11 @@ public class RequestProvidersListC2SPacket implements CustomPacketPayload {
long encodedId = -1L - i; // 约定负数代表按索引 long encodedId = -1L - i; // 约定负数代表按索引
idxIds.add(encodedId); idxIds.add(encodedId);
names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c)); names.add(ExtendedAEPatternUploadUtil.getProviderDisplayName(c));
i18nKeys.add(ExtendedAEPatternUploadUtil.getProviderI18nName(c));
slots.add(empty); slots.add(empty);
} }
player.connection.send(new ProvidersListS2CPacket(idxIds, names, slots)); player.connection.send(new ProvidersListS2CPacket(idxIds, names, i18nKeys, slots));
}
}); });
} }
} }

View File

@ -38,6 +38,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/** /**
* ExtendedAE扩展样板管理终端专用的样板上传工具类 * ExtendedAE扩展样板管理终端专用的样板上传工具类
@ -124,10 +125,10 @@ public class ExtendedAEPatternUploadUtil {
} }
// 最近一次通过 JEI 填充到编码终端的处理配方的中文名称烧炼/高炉/烟熏... // 最近一次通过 JEI 填充到编码终端的处理配方的中文名称烧炼/高炉/烟熏...
public static volatile String lastProcessingName = null; public static volatile List<String> lastProcessingNameList = new ArrayList<>();
public static void setLastProcessingName(String name) { public static void addLastProcessingNameList(String name) {
lastProcessingName = name; lastProcessingNameList.add(name);
} }
/** /**
@ -257,19 +258,14 @@ public class ExtendedAEPatternUploadUtil {
String id = key.toString(); String id = key.toString();
String path = key.getPath(); String path = key.getPath();
// 常见原版类型映射 // 常见原版类型映射
switch (path) { return switch (path) {
case "smelting": case "smelting" -> "熔炉"; // 熔炉
return "熔炉"; // 熔炉 case "blasting" -> "高炉";
case "blasting": case "smoking" -> "烟熏";
return "高炉"; case "campfire_cooking" -> "营火";
case "smoking":
return "烟熏";
case "campfire_cooking":
return "营火";
default:
// 其他模组类型若未配置中文则返回原始IDnamespace:path作为英文回退 // 其他模组类型若未配置中文则返回原始IDnamespace:path作为英文回退
return id; default -> id;
} };
} }
/** /**
@ -283,13 +279,14 @@ public class ExtendedAEPatternUploadUtil {
ResourceLocation key = BuiltInRegistries.RECIPE_TYPE.getKey(type); ResourceLocation key = BuiltInRegistries.RECIPE_TYPE.getKey(type);
if (key == null) return null; if (key == null) return null;
// 先查别名 path 匹配 // 先查别名 path 匹配
String alias = CUSTOM_ALIASES.get(key.getPath().toLowerCase()); // 别名替换放在init做, 提前别名的次序
if (alias != null && !alias.isBlank()) return alias; // String alias = CUSTOM_ALIASES.get(key.getPath().toLowerCase());
// if (alias != null && !alias.isBlank()) return alias;
// 再查完整ID映射 // 再查完整ID映射
String custom = CUSTOM_NAMES.get(key); // String custom = CUSTOM_NAMES.get(key);
if (custom != null && !custom.isBlank()) { // if (custom != null && !custom.isBlank()) {
return custom; // return custom;
} // }
return key.getPath(); return key.getPath();
} }
@ -370,6 +367,25 @@ public class ExtendedAEPatternUploadUtil {
return s; return s;
} }
public static String findMapping(String key) {
if (key == null || key.isBlank()) return null;
if (CUSTOM_ALIASES.containsKey(key.toLowerCase()))
return CUSTOM_ALIASES.get(key.toLowerCase());
if (key.contains(":")) {
try {
ResourceLocation location = ResourceLocation.tryParse(key);
if (location != null && CUSTOM_NAMES.containsKey(location))
return CUSTOM_NAMES.get(location);
} catch (Exception ignored) {}
}
if (!Pattern.matches(".*[:._/\\-].*", key)) return key;
return null;
}
/** /**
* 获取玩家当前的样板访问终端菜单支持ExtendedAE和原版AE2 * 获取玩家当前的样板访问终端菜单支持ExtendedAE和原版AE2
* *
@ -471,8 +487,7 @@ public class ExtendedAEPatternUploadUtil {
// 收集所有可用的装配矩阵图样模块内部库存并逐一尝试遵循其过滤规则 // 收集所有可用的装配矩阵图样模块内部库存并逐一尝试遵循其过滤规则
List<InternalInventory> inventories = findAllMatrixPatternInventories(grid); List<InternalInventory> inventories = findAllMatrixPatternInventories(grid);
if (!inventories.isEmpty()) { if (!inventories.isEmpty()) {
for (int i = 0; i < inventories.size(); i++) { for (InternalInventory inv : inventories) {
var inv = inventories.get(i);
ItemStack toInsert = stack.copy(); ItemStack toInsert = stack.copy();
ItemStack remain = inv.addItems(toInsert); ItemStack remain = inv.addItems(toInsert);
if (remain.getCount() < stack.getCount()) { if (remain.getCount() < stack.getCount()) {
@ -491,8 +506,7 @@ public class ExtendedAEPatternUploadUtil {
// 回退尝试 Forge 能力可能为聚合图样仓同样遍历所有矩阵 // 回退尝试 Forge 能力可能为聚合图样仓同样遍历所有矩阵
List<?> handlers = findAllMatrixPatternHandlers(grid); List<?> handlers = findAllMatrixPatternHandlers(grid);
if (!handlers.isEmpty()) { if (!handlers.isEmpty()) {
for (int i = 0; i < handlers.size(); i++) { for (Object cap : handlers) {
var cap = handlers.get(i);
ItemStack toInsert = stack.copy(); ItemStack toInsert = stack.copy();
ItemStack remain = insertIntoAnySlot(cap, toInsert); ItemStack remain = insertIntoAnySlot(cap, toInsert);
if (remain.getCount() < stack.getCount()) { if (remain.getCount() < stack.getCount()) {
@ -871,6 +885,32 @@ public class ExtendedAEPatternUploadUtil {
return "样板供应器 #" + providerId; return "样板供应器 #" + providerId;
} }
/** 获取供应器显示名(优先组名) */
public static String getProviderDisplayName(PatternContainer container) {
if (container == null) return "未知供应器";
try {
var group = container.getTerminalGroup();
if (group != null) return group.name().getString();
} catch (Throwable ignored) {
}
return "样板供应器";
}
public static String getProviderI18nName(Long providerId, PatternAccessTermMenu menu) {
return getProviderI18nName(getPatternContainerById(menu, providerId));
}
/** 获取样板供应器默认选取的方块名称 */
public static String getProviderI18nName(PatternContainer container) {
if (container == null) return "";
try {
var group = container.getTerminalGroup();
var name = group.name();
return name.toString().contains("literal") ? "" : name.toString();
} catch (Throwable ignored) {}
return "";
}
/** /**
* 验证样板供应器是否可用 * 验证样板供应器是否可用
* *
@ -1096,7 +1136,10 @@ public class ExtendedAEPatternUploadUtil {
if (inv == null || inv.size() <= 0) continue; if (inv == null || inv.size() <= 0) continue;
boolean hasEmpty = false; boolean hasEmpty = false;
for (int i = 0; i < inv.size(); i++) { for (int i = 0; i < inv.size(); i++) {
if (inv.getStackInSlot(i).isEmpty()) { hasEmpty = true; break; } if (inv.getStackInSlot(i).isEmpty()) {
hasEmpty = true;
break;
}
} }
if (hasEmpty) list.add(container); if (hasEmpty) list.add(container);
} }
@ -1107,17 +1150,6 @@ public class ExtendedAEPatternUploadUtil {
return list; return list;
} }
/** 获取供应器显示名(优先组名) */
public static String getProviderDisplayName(PatternContainer container) {
if (container == null) return "未知供应器";
try {
var group = container.getTerminalGroup();
if (group != null) return group.name().getString();
} catch (Throwable ignored) {
}
return "样板供应器";
}
/** 计算供应器空槽位数量 */ /** 计算供应器空槽位数量 */
public static int getAvailableSlots(PatternContainer container) { public static int getAvailableSlots(PatternContainer container) {
if (container == null) return -1; if (container == null) return -1;

View File

@ -19,6 +19,7 @@ public class GetKey {
public static final String GUI = "gui"; public static final String GUI = "gui";
public static final String MESSAGE = "message"; public static final String MESSAGE = "message";
public static final String CONFIG = "config"; public static final String CONFIG = "config";
public static final String TOOLTIP = "tooltip";
public GetKey() { public GetKey() {
this.keyType = ""; this.keyType = "";

View File

@ -31,6 +31,7 @@
"item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静", "item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物", "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物",
"tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰", "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰",
"tooltip.extendedae_plus.encode_button_description": "\n§a[Shift] §7上传样板",
"config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息", "config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息",
"config.jade.plugin_extendedae_plus.wt_frequency": "显示频率", "config.jade.plugin_extendedae_plus.wt_frequency": "显示频率",

View File

@ -25,6 +25,7 @@
"ae2.menu.PatternProviderMenuAdvancedMixin", "ae2.menu.PatternProviderMenuAdvancedMixin",
"ae2.menu.PatternProviderMenuDoublingMixin", "ae2.menu.PatternProviderMenuDoublingMixin",
"emi.MixinEncodePatternTransfer", "emi.MixinEncodePatternTransfer",
"emi.MixinRecipeFillButton",
"extendedae.common.PartExPatternProviderMixin", "extendedae.common.PartExPatternProviderMixin",
"extendedae.common.TileExPatternProviderMixin", "extendedae.common.TileExPatternProviderMixin",
"extendedae.container.ContainerExPatternProviderMixin", "extendedae.container.ContainerExPatternProviderMixin",
@ -43,6 +44,7 @@
"ae2.accessor.PatternAccessTermScreenSlotsRowAccessor", "ae2.accessor.PatternAccessTermScreenSlotsRowAccessor",
"ae2.client.gui.AEBaseScreenMixin", "ae2.client.gui.AEBaseScreenMixin",
"ae2.client.gui.InterfaceScreenMixin", "ae2.client.gui.InterfaceScreenMixin",
"ae2.client.gui.MixinActionButton",
"ae2.client.gui.PatternEncodingTermScreenMixin", "ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.PatternProviderCloseMixin", "ae2.client.gui.PatternProviderCloseMixin",
"ae2.client.gui.PatternProviderScreenMixin", "ae2.client.gui.PatternProviderScreenMixin",