1.制作中打开供应器高亮对应样板
2.优化高亮工具类
This commit is contained in:
C-H716 2025-09-02 02:05:16 +08:00
parent 247081939f
commit f254da43f4
7 changed files with 222 additions and 96 deletions

View File

@ -0,0 +1,47 @@
package com.extendedae_plus.content;
import appeng.api.crafting.IPatternDetails;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
public final class PatternHighlightStore {
// 使用同步的 WeakHashMap 存储高亮状态键为 IPatternDetails值为 Boolean
private static final Map<IPatternDetails, Boolean> HIGHLIGHTS = Collections.synchronizedMap(new WeakHashMap<>());
// 私有构造方法防止实例化
private PatternHighlightStore() {}
/**
* 设置指定 details 的高亮状态
* @param details 需要设置的 IPatternDetails 实例
* @param highlighted 是否高亮
*/
public static void setHighlight(IPatternDetails details, boolean highlighted) {
if (details == null) return;
if (highlighted) {
HIGHLIGHTS.put(details, Boolean.TRUE); // 设置为高亮
} else {
HIGHLIGHTS.remove(details); // 移除高亮
}
}
/**
* 获取指定 details 的高亮状态
* @param details 需要查询的 IPatternDetails 实例
* @return 是否高亮
*/
public static boolean getHighlight(IPatternDetails details) {
if (details == null) return false;
Boolean v = HIGHLIGHTS.get(details);
return v != null && v;
}
/**
* 清空所有高亮状态在供应器界面关闭时调用
*/
public static void clearAll() {
HIGHLIGHTS.clear();
}
}

View File

@ -1,5 +1,6 @@
package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.stacks.AEKey;
import appeng.client.Point;
import appeng.client.gui.AEBaseScreen;
@ -12,12 +13,14 @@ import appeng.client.gui.style.Text;
import appeng.client.gui.style.TextAlignment;
import appeng.menu.slot.AppEngSlot;
import com.extendedae_plus.api.ExPatternPageAccessor;
import com.extendedae_plus.content.PatternHighlightStore;
import com.extendedae_plus.network.CraftingMonitorJumpC2SPacket;
import com.extendedae_plus.network.CraftingMonitorOpenProviderC2SPacket;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternProvider;
import com.mojang.logging.LogUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.Rect2i;
@ -150,40 +153,49 @@ public abstract class AEBaseScreenMixin {
@Inject(method = "renderSlot", at = @At("TAIL"))
private void eap$renderSlotAmounts(GuiGraphics guiGraphics, Slot s, CallbackInfo ci) {
Object self = this;
// 只处理AppEngSlot类型的槽位
if (!(s instanceof AppEngSlot appEngSlot)) {
return;
}
// 检查槽位是否可见且有效
if (!appEngSlot.isActive() || !appEngSlot.isSlotEnabled()) {
return;
}
// 获取槽位中的物品
var itemStack = appEngSlot.getItem();
if (itemStack.isEmpty()) {
return;
}
// 使用GuiUtil的格式化方法获取数量文本
String amountText = GuiUtil.getPatternOutputText(itemStack);
if (amountText.isEmpty()) {
return;
}
// 在槽位右下角绘制数量文本
Font font = eap$getFont(self);
GuiUtil.drawAmountText(guiGraphics, font, amountText, appEngSlot.x, appEngSlot.y, 0.6f);
try {
var details = PatternDetailsHelper.decodePattern(itemStack, Minecraft.getInstance().level, false);
if (PatternHighlightStore.getHighlight(details)) {
try {
GuiUtil.drawSlotRainbowHighlight(guiGraphics, s.x, s.y);
} catch (Throwable ignored) {}
}
} catch (Throwable ignore) {}
}
// AEBaseScreen.drawText 完成某个文本绘制后若该文本为样板标签则紧接着绘制页码
@Inject(method = "drawText", at = @At("TAIL"), remap = false)
private void eap$appendPageAfterPatternsLabel(GuiGraphics guiGraphics,
Text text,
@Nullable TextOverride override,
CallbackInfo ci) {
Text text,
@Nullable TextOverride override,
CallbackInfo ci) {
Object self = this;
if (!(self instanceof GuiExPatternProvider)) {
return;
@ -237,8 +249,10 @@ public abstract class AEBaseScreenMixin {
if (!isPatterns) {
String label = content.getString();
if (label != null) {
if (label.equals(Component.translatable("gui.pattern_provider.patterns").getString())) isPatterns = true;
else if (label.equals(Component.translatable("gui.extendedae.patterns").getString())) isPatterns = true;
if (label.equals(Component.translatable("gui.pattern_provider.patterns").getString()))
isPatterns = true;
else if (label.equals(Component.translatable("gui.extendedae.patterns").getString()))
isPatterns = true;
else if (label.equals(Component.translatable("gui.ae2.patterns").getString())) isPatterns = true;
}
}
@ -263,16 +277,18 @@ public abstract class AEBaseScreenMixin {
if (v instanceof Integer i) {
max = Math.max(1, i);
}
} catch (Throwable ignored) {}
} catch (Throwable ignored) {
}
String pageText = ""+cur+"" + "/" + max + "";
String pageText = "" + cur + "" + "/" + max + "";
ScreenStyle style = eap$getStyle(self);
int color = 0xFFFFFFFF;
if (style != null) {
try {
color = style.getColor(PaletteColor.DEFAULT_TEXT_COLOR).toARGB();
} catch (Throwable ignored) {}
} catch (Throwable ignored) {
}
}
int padding = 4;
if (scale == 1.0f) {
@ -284,6 +300,7 @@ public abstract class AEBaseScreenMixin {
guiGraphics.drawString(font, pageText, lineWidth + padding, 0, color, false);
guiGraphics.pose().popPose();
}
} catch (Throwable ignored) {}
} catch (Throwable ignored) {
}
}
}

View File

@ -0,0 +1,28 @@
package com.extendedae_plus.mixin.ae2.client.gui;
import appeng.client.gui.implementations.PatternProviderScreen;
import com.extendedae_plus.content.PatternHighlightStore;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.world.inventory.AbstractContainerMenu;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = AbstractContainerScreen.class, remap = false)
public class PatternProviderCloseMixin {
@Shadow
protected AbstractContainerMenu menu;
@Inject(method = "removed", at = @At("HEAD"))
private void onRemoved(CallbackInfo ci) {
try {
if (((Object) this) instanceof PatternProviderScreen) {
PatternHighlightStore.clearAll();
}
} catch (Throwable ignored) {
}
}
}

View File

@ -4,7 +4,6 @@ import appeng.api.crafting.PatternDetailsHelper;
import appeng.client.gui.AEBaseScreen;
import appeng.client.gui.Icon;
import appeng.client.gui.me.patternaccess.PatternContainerRecord;
import appeng.client.gui.me.patternaccess.PatternSlot;
import appeng.client.gui.style.ScreenStyle;
import appeng.client.gui.widgets.AETextField;
import appeng.client.gui.widgets.IconButton;
@ -13,6 +12,7 @@ import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.mixin.extendedae.accessor.GuiExPatternTerminalAccessor;
import com.extendedae_plus.network.ModNetwork;
import com.extendedae_plus.network.OpenProviderUiC2SPacket;
import com.extendedae_plus.util.GuiUtil;
import com.glodblock.github.extendedae.client.gui.GuiExPatternTerminal;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
@ -76,41 +76,6 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
super(menu, playerInventory, title, style);
}
@Unique
private static int eap$withAlpha(int rgb, int alpha255) {
return ((alpha255 & 0xFF) << 24) | (rgb & 0x00FFFFFF);
}
/**
* HSV 转换为 RGB返回 0xRRGGBB不含 alpha
* h: 0.0~1.0s: 0.0~1.0v: 0.0~1.0
*/
@Unique
private static int eap$hsvToRgb(float h, float s, float v) {
if (s <= 0.0f) {
int g = Math.round(v * 255.0f);
return (g << 16) | (g << 8) | g;
}
float hh = (h - (float) Math.floor(h)) * 6.0f;
int sector = (int) Math.floor(hh);
float f = hh - sector;
float p = v * (1.0f - s);
float q = v * (1.0f - s * f);
float t = v * (1.0f - s * (1.0f - f));
float r, g, b;
switch (sector) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q; break;
}
int ri = Math.round(r * 255.0f);
int gi = Math.round(g * 255.0f);
int bi = Math.round(b * 255.0f);
return (ri << 16) | (gi << 8) | bi;
}
/**
* 获取当前选择的样板供应器ID
@ -579,53 +544,9 @@ public abstract class GuiExPatternTerminalMixin extends AEBaseScreen<AEBaseMenu>
return;
}
// 彩虹色的流转基于时间在 HSV 色环上循环4 秒为一周期
long now = System.currentTimeMillis();
final long rainbowPeriodMs = 4000L;
float hue = (now % rainbowPeriodMs) / (float) rainbowPeriodMs; // 0.0 ~ 1.0
int rainbowRgb = eap$hsvToRgb(hue, 1.0f, 1.0f);
// 使用 GuiUtil 的通用绘制方法绘制槽位高亮包含彩虹流转效果
GuiUtil.drawPatternSlotHighlights(guiGraphics, this.menu.slots, this.matchedStack, this.matchedProvider);
for (Slot slot : this.menu.slots) {
if (!(slot instanceof PatternSlot ps)) {
continue;
}
int sx = slot.x;
int sy = slot.y;
boolean isMatchedSlot = this.matchedStack != null && this.matchedStack.contains(slot.getItem());
boolean isMatchedProvider = false;
try {
PatternContainerRecord container = ps.getMachineInv();
isMatchedProvider = this.matchedProvider != null && this.matchedProvider.contains(container);
} catch (Throwable ignored) {
}
// 依据命中状态选择颜色方案
int borderColor;
int backgroundColor;
if (isMatchedSlot) {
// 命中槽位使用彩虹色边框与浅底色固定透明度呈现色相流转效果
borderColor = eap$withAlpha(rainbowRgb, 0xA0);
backgroundColor = eap$withAlpha(rainbowRgb, 0x3C);
} else if (!isMatchedProvider) {
borderColor = eap$withAlpha(0xFFFFFF, 0x40);
backgroundColor = eap$withAlpha(0x000000, 0x18);
} else {
borderColor = eap$withAlpha(0xFFFFFF, 0x30);
backgroundColor = eap$withAlpha(0xFFFFFF, 0x14);
}
// 绘制 18x18 边框1px
eap$fill(guiGraphics, new Rect2i(sx - 1, sy - 1, 18, 1), borderColor);
eap$fill(guiGraphics, new Rect2i(sx - 1, sy + 16, 18, 1), borderColor);
eap$fill(guiGraphics, new Rect2i(sx - 1, sy, 1, 16), borderColor);
eap$fill(guiGraphics, new Rect2i(sx + 16, sy, 1, 16), borderColor);
// 绘制 16x16 浅底色半透明叠加在槽位上方
eap$fill(guiGraphics, new Rect2i(sx, sy, 16, 16), backgroundColor);
}
}
@Unique

View File

@ -11,6 +11,7 @@ import appeng.menu.AEBaseMenu;
import appeng.menu.locator.MenuLocators;
import appeng.menu.me.crafting.CraftingCPUMenu;
import appeng.parts.AEBasePart;
import com.extendedae_plus.content.PatternHighlightStore;
import com.extendedae_plus.mixin.ae2.accessor.PatternProviderLogicAccessor;
import com.mojang.logging.LogUtils;
import net.minecraft.network.FriendlyByteBuf;
@ -99,6 +100,9 @@ public class CraftingMonitorOpenProviderC2SPacket {
} else {
host.openMenu(player, MenuLocators.forBlockEntity(pbe));
}
PatternHighlightStore.setHighlight(pattern, true);
context.setPacketHandled(true);
return;
} catch (Throwable t) {

View File

@ -2,11 +2,17 @@ package com.extendedae_plus.util;
import appeng.api.crafting.PatternDetailsHelper;
import appeng.api.stacks.GenericStack;
import appeng.client.gui.me.patternaccess.PatternContainerRecord;
import appeng.client.gui.me.patternaccess.PatternSlot;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import java.util.List;
import java.util.Set;
/**
* GUI工具类提供样板获取绘制等通用功能
@ -77,4 +83,105 @@ public class GuiUtil {
guiGraphics.drawString(font, text, (int)(textX / scale), (int)(textY / scale), 0xFFFFFFFF, true);
guiGraphics.pose().popPose();
}
// Helper: add alpha channel to RGB (rgb is 0xRRGGBB)
private static int withAlpha(int rgb, int alpha255) {
return ((alpha255 & 0xFF) << 24) | (rgb & 0x00FFFFFF);
}
// HSV -> RGB (returns 0xRRGGBB)
private static int hsvToRgb(float h, float s, float v) {
if (s <= 0.0f) {
int g = Math.round(v * 255.0f);
return (g << 16) | (g << 8) | g;
}
float hh = (h - (float) Math.floor(h)) * 6.0f;
int sector = (int) Math.floor(hh);
float f = hh - sector;
float p = v * (1.0f - s);
float q = v * (1.0f - s * f);
float t = v * (1.0f - s * (1.0f - f));
float r, g, b;
switch (sector) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q; break;
}
int ri = Math.round(r * 255.0f);
int gi = Math.round(g * 255.0f);
int bi = Math.round(b * 255.0f);
return (ri << 16) | (gi << 8) | bi;
}
// 返回当前时间对应的彩虹色0xRRGGBB周期固定为 4000ms
private static int getRainbowRgb() {
long now = System.currentTimeMillis();
final long rainbowPeriodMs = 4000L;
float hue = (now % rainbowPeriodMs) / (float) rainbowPeriodMs; // 0.0 ~ 1.0
return hsvToRgb(hue, 1.0f, 1.0f);
}
// 在给定槽位坐标绘制 1px 边框18x18 16x16 半透明背景
private static void drawSlotBox(GuiGraphics guiGraphics, int sx, int sy, int borderColor, int backgroundColor) {
guiGraphics.fill(sx - 1, sy - 1, sx + 17, sy, borderColor);
guiGraphics.fill(sx - 1, sy + 16, sx + 17, sy + 17, borderColor);
guiGraphics.fill(sx - 1, sy, sx, sy + 16, borderColor);
guiGraphics.fill(sx + 16, sy, sx + 17, sy + 16, borderColor);
guiGraphics.fill(sx, sy, sx + 16, sy + 16, backgroundColor);
}
/**
* 在槽位上绘制彩色流转的高亮和浅底色
*/
public static void drawPatternSlotHighlights(GuiGraphics guiGraphics, List<Slot> slots, Set<ItemStack> matchedStack, Set<PatternContainerRecord> matchedProvider) {
if (slots == null) return;
int rainbowRgb = getRainbowRgb();
for (Slot slot : slots) {
if (!(slot instanceof PatternSlot ps)) {
continue;
}
int sx = slot.x;
int sy = slot.y;
boolean isMatchedSlot = matchedStack != null && matchedStack.contains(slot.getItem());
boolean isMatchedProvider = false;
try {
PatternContainerRecord container = ps.getMachineInv();
isMatchedProvider = matchedProvider != null && matchedProvider.contains(container);
} catch (Throwable ignored) {
}
int borderColor;
int backgroundColor;
if (isMatchedSlot) {
borderColor = withAlpha(rainbowRgb, 0xA0);
backgroundColor = withAlpha(rainbowRgb, 0x3C);
} else if (!isMatchedProvider) {
borderColor = withAlpha(0xFFFFFF, 0x40);
backgroundColor = withAlpha(0x000000, 0x18);
} else {
borderColor = withAlpha(0xFFFFFF, 0x30);
backgroundColor = withAlpha(0xFFFFFF, 0x14);
}
drawSlotBox(guiGraphics, sx, sy, borderColor, backgroundColor);
}
}
/**
* 在指定槽位坐标绘制彩虹流转的边框与浅底色用于非 PatternSlot 的高亮场景
*/
public static void drawSlotRainbowHighlight(GuiGraphics guiGraphics, int sx, int sy) {
int rainbowRgb = getRainbowRgb();
int borderColor = withAlpha(rainbowRgb, 0xA0);
int backgroundColor = withAlpha(rainbowRgb, 0x3C);
drawSlotBox(guiGraphics, sx, sy, borderColor, backgroundColor);
}
}

View File

@ -15,6 +15,7 @@
"ae2.accessor.PatternAccessTermScreenSlotsRowAccessor",
"ae2.client.gui.AEBaseScreenMixin",
"ae2.client.gui.PatternEncodingTermScreenMixin",
"ae2.client.gui.PatternProviderCloseMixin",
"ae2.client.gui.PatternProviderScreenMixin",
"ae2.client.gui.SlotGridLayoutMixin",
"extendedae.accessor.GuiExPatternTerminalAccessor",
@ -30,6 +31,7 @@
"mixins": [
"ae2.AEProcessingPatternMixin",
"ae2.CraftingCPUClusterMixin",
"ae2.IPatternDetailsMixin",
"ae2.accessor.MEStorageMenuAccessor",
"ae2.accessor.PatternEncodingTermMenuAccessor",
"ae2.accessor.PatternProviderLogicAccessor",