Coremod 与 Mixin(Coremod / Mixin)
基本信息
| 属性 | 值 |
|---|---|
coreModClass |
core.CraftingTweaksCore |
| Coremod 全限定名 | com.gtnewhorizons.craftingtweaks.core.CraftingTweaksCore |
usesMixins |
true |
usesMixinDebug |
true |
mixinsPackage |
mixins.early |
separateMixinSourceSet |
空(Mixin 与主代码同在 src/main) |
mixinPlugin |
空(未使用自定义 IMixinConfigPlugin) |
accessTransformersFile |
空 |
forceEnableMixins |
false |
containsMixinsAndOrCoreModOnly |
false |
| Mixin 数量 | 3 个(全在 mixins/early) |
⚠️ 本 mod 没有 Access Transformer
gradle.properties 中 accessTransformersFile =(空值),
CraftingTweaksCore.getAccessTransformerClass() 明确 return null,
且 src/main/resources/META-INF/ 目录根本不存在
(ls src/main/resources/META-INF → No such file or directory)。
权限提升完全靠 Mixin @Accessor / @Invoker 实现,
Coremod 的 ASM transformer 列表也是 getASMTransformerClass() → null,
即没有任何字节码改写,唯一的 coremod 职责是「提前加载 Mixin」。
Coremod:CraftingTweaksCore
| 项 | 值 |
|---|---|
| 包路径 | com.gtnewhorizons.craftingtweaks.core.CraftingTweaksCore |
| 插件名 | @IFMLLoadingPlugin.Name("CraftingTweaks-Core") |
| MC 版本 | @IFMLLoadingPlugin.MCVersion("1.7.10") |
| 实现接口 | IFMLLoadingPlugin、IEarlyMixinLoader(gtnhmixins) |
IFMLLoadingPlugin 各方法返回值:
| 方法 | 返回 |
|---|---|
getMixinConfig() |
"mixins.craftingtweaks.early.json" |
getASMTransformerClass() |
null |
getModContainerClass() |
null |
getSetupClass() |
null |
getAccessTransformerClass() |
null |
injectData(Map<String, Object>) |
空实现(no-op) |
getMixins(Set<String> loadedCoreMods) 的返回值按侧分叉:
| 侧 | 返回的 Mixin 列表 |
|---|---|
| 客户端 | minecraft.GuiScreenAccessor、minecraft.GuiContainerAccessor、minecraft.MixinGuiScreen_CraftingTweaks |
| 服务端 | Collections.emptyList() |
即三个 Mixin 全部只在客户端应用,服务端完全不加载。
Mixin 清单
两个 Mixin 配置 JSON,package 字段只在 early 那个里声明:
| 配置文件 | package |
refmap |
target |
minVersion |
|---|---|---|---|---|
mixins.craftingtweaks.early.json |
com.gtnewhorizons.craftingtweaks.mixins.early |
mixins.craftingtweaks.refmap.json |
@env(DEFAULT) |
0.8.5-GTNH |
mixins.craftingtweaks.json |
无 | 同上 | @env(DEFAULT) |
0.8.5-GTNH |
mixins.craftingtweaks.json 不含 mixins / client / server 任何条目,
即空配置;它由 mixinsPackage 自动生成的注解处理器产出,实际生效的只有
early 配置(coremod 主动指定)。
两者 compatibilityLevel 均为 JAVA_8。
目标类 1:GuiScreenAccessor
@Mixin(GuiScreen.class)
public interface GuiScreenAccessor
| 成员 | 注解 | 目标 | 本 mod 内调用点 |
|---|---|---|---|
List<GuiButton> getButtonList() |
@Accessor |
字段 buttonList(隐式推导) |
0 处(无调用方) |
void drawHoveringText(List<String> textLines, int x, int y) |
@Invoker(value = "func_146283_a") |
SRG func_146283_a |
ClientProxy.onDrawScreen 1 处 |
drawHoveringText 的 SRG 名在源码中以字符串形式显式给出(func_146283_a),
这是唯一一处直接书写 SRG 名的地方。
目标类 2:GuiContainerAccessor
@Mixin(GuiContainer.class)
public interface GuiContainerAccessor extends GuiScreenAccessor
| 成员 | 注解 | 目标字段/方法 | 本 mod 内调用点数 |
|---|---|---|---|
int getXSize() |
@Accessor |
xSize(隐式) |
2(Gany’s、TFC 的 initGui) |
int getYSize() |
@Accessor |
ySize(隐式) |
0 |
int getGuiLeft() |
@Accessor |
guiLeft(隐式) |
4(Gany’s/TFC 各 1,GuiTweakButton 构造 + drawButton) |
int getGuiTop() |
@Accessor |
guiTop(隐式) |
4(同上) |
void setIgnoreMouseUp(boolean ignoreMouseUp) |
@Accessor(value = "field_146995_H") |
SRG field_146995_H |
0 |
boolean getDragSplitting() |
@Accessor(value = "field_147007_t") |
SRG field_147007_t |
0 |
void setDragSplitting(boolean dragSplitting) |
@Accessor(value = "field_147007_t") |
SRG field_147007_t |
0 |
int getDragSplittingButton() |
@Accessor(value = "field_146988_G") |
SRG field_146988_G |
16(压缩路径每次拖拽都读) |
Slot callGetSlotAtPosition(int x, int y) |
@Invoker |
getSlotAtPosition(隐式) |
2(onGuiClick + onDrawScreen) |
void callHandleMouseClick(Slot slot, int slotId, int mouseButton, int clickType) |
@Invoker |
handleMouseClick(隐式) |
0 |
⚠️ 三个 SRG 字段名(field_146995_H、field_147007_t、field_146988_G)
在本 mod 源码中只出现在 Accessor 声明处,没有任何调用方。
压缩流程用的是 field_146988_G(getDragSplittingButton)这一条 ——
它被 ClientProvider.compress 的拖拽分配调用了 16 次(ClientProvider.java
第 385/387/389/397/399/401/403/405/407/418/422/426/428/430/432/434 行;
第 457 行是私有辅助方法 getDragSplittingButton(int, int) 的定义,
不计入调用点);
另外两个字段的 getter/setter 是预留但未使用的声明。
同理 getYSize() 与 callHandleMouseClick(...) 也是零调用方的声明。
⚠️ GuiContainerAccessor 继承 GuiScreenAccessor,
所以 drawHoveringText 这条 @Invoker 在 GuiContainer 上也可用 ——
ClientProxy.onDrawScreen 正是靠 event.gui instanceof GuiScreenAccessor
的 instanceof 判定(而非 GuiContainerAccessor)来决定能否画 tooltip。
目标类 3:MixinGuiScreen_CraftingTweaks
@Mixin(GuiScreen.class)
public class MixinGuiScreen_CraftingTweaks {
@Inject(
method = "handleMouseInput",
at = @At(value = "INVOKE_ASSIGN",
target = "Lorg/lwjgl/input/Mouse;getEventButton()I",
remap = false),
cancellable = true)
private void craftingtweaks$handleMouseInput(CallbackInfo ci,
@Local(ordinal = 0) int mouseX,
@Local(ordinal = 1) int mouseY,
@Local(ordinal = 2) int btn) { ... }
}
| 项 | 值 |
|---|---|
| 目标方法 | GuiScreen.handleMouseInput(隐式 SRG 名,未写字符串) |
| 注入点 | @At(value = "INVOKE_ASSIGN", target = "Lorg/lwjgl/input/Mouse;getEventButton()I", remap = false) |
remap = false |
因为目标是 LWJGL 的 Mouse,不是 Minecraft 方法 |
| 捕获的局部变量 | 通过 MixinExtras @Local(ordinal 0/1/2 = mouseX / mouseY / btn) |
| 回调体 | if (CraftingTweaks.onGuiClick(mouseX, mouseY, btn)) ci.cancel(); |
这是唯一会取消原版行为的注入:当 onGuiClick 返回 true 时,
handleMouseInput 被中断,从而阻断后续的鼠标点击分发。
注入点选在 INVOKE_ASSIGN(把 Mouse.getEventButton() 结果赋给局部变量处)
而非 HEAD,目的是确保三个 @Local 能按 ordinal 正确取到值。
@Local 来自 com.llamalad7.mixinextras.sugar.Local,
说明构建期依赖 MixinExtras。
GuiClickEvent 链路
CraftingTweaks.onGuiClick(mouseX, mouseY, btn):
- 仅当
Mouse.getEventButtonState()为真(鼠标按键刚按下)才继续 - 组装
GuiClickEvent(currentScreen, mouseX, mouseY, button) MinecraftForge.EVENT_BUS.post(event)- 返回
event.isCanceled()
GuiClickEvent 是 @Cancelable 的 cpw.mods.fml.common.eventhandler.Event,
三个字段全 public final:gui(GuiScreen)、mouseX、mouseY、button。
消费方只有 ClientProxy.onGuiClick(@SubscribeEvent(priority = EventPriority.LOWEST)),
用于 W 键正向点击投料,见 合成网格操作。
NEI 热键屏蔽
NEIHotkeyCheck implements HotkeyCheck:
public boolean allowHotkeys() {
return LayoutManager.getInputFocused() == null;
}
通过 FMLPostInitializationEvent.buildSoftDependProxy("NotEnoughItems", "...")
软依赖加载 —— NEI 未安装时 hotkeyCheck == null,判定短路放行。
加载的类直接 import codechicken.nei.LayoutManager,故 NEI 是软依赖而非硬依赖。
ClientProxy.onClientTick 开头:
if ((hotkeyCheck != null && !hotkeyCheck.allowHotkeys())) return;
NEI 物品搜索框聚焦时,全部 6 个热键一并失效。