[全流程完成]
This commit is contained in:
@@ -18,5 +18,3 @@ progress.md
|
|||||||
/.reasonix/
|
/.reasonix/
|
||||||
/.tmp_*/
|
/.tmp_*/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
/Issue-改善物品方块扫描要素.md
|
|
||||||
/路径.md
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# [Feature Request] 改善物品/方块扫描要素的自动赋予机制(恢复配方溯源 + 性能优化)
|
||||||
|
|
||||||
|
## 概述 Summary
|
||||||
|
|
||||||
|
请求为 TC4 1.21.1 移植版恢复 1.7.10 的**合成配方溯源**机制(物品作为配方产出时,按原料要素自动赋予要素),并修复 `getObjectTags` 无索引穷举遍历导致的严重性能问题。
|
||||||
|
|
||||||
|
当前移植版 `ItemAspectRegistry.getObjectTags` 只保留:注册表 → NBT → 精确表 → ID 词根推算 → `ENTROPY` 兜底。**词根表仅覆盖约 15 组 ID**,对 mod 物品几乎不命中,大量物品退化为 `ENTROPY×1` 或词根给的 1-2 种要素,且无法反映合成复杂度。
|
||||||
|
|
||||||
|
## 问题描述 Problem
|
||||||
|
|
||||||
|
### 功能缺失(影响整个 mod 生态)
|
||||||
|
|
||||||
|
1. **要素不准确**:mod 物品(如 `thaumicenergistics:essentia_provider`、`waystones:waystone`)扫描要素靠 ID 词根猜——词根对不上就 `ENTROPY×1`,对得上也只有 1-3 种
|
||||||
|
2. **无合成溯源**:合成材料完全不影响要素——1 个原料的和 10 个原料的合成品要素一样,无法体现合成复杂度
|
||||||
|
|
||||||
|
### 性能严重(实测)
|
||||||
|
|
||||||
|
- `getObjectTags` 每次调用**穷举遍历全部配方**(1932+ 个 CRAFTING)比较产出——无索引、无缓存
|
||||||
|
- 客户端 JEI 启动遍历全部物品时触发全量扫描:**实测单次扫描耗时 10 秒 / 1127 tick**(F3+L profiler)
|
||||||
|
- 频繁调用(Thaumonomicon/JEI 显示)导致持续卡顿
|
||||||
|
|
||||||
|
## 期望行为 Expected Behavior
|
||||||
|
|
||||||
|
- mod 物品扫描要素**按合成材料赋予**(与 1.7.10 一致)
|
||||||
|
- 要素数量/种类**合理**(种类 2-6、总量 8-24、单要素 ≤64)
|
||||||
|
- 扫描/显示**无卡顿**(单次查询毫秒级)
|
||||||
|
|
||||||
|
## 建议实现 Suggested Implementation
|
||||||
|
|
||||||
|
### A. 功能:getObjectTags 增加配方溯源分支
|
||||||
|
|
||||||
|
```
|
||||||
|
在词根推算 / ENTROPY 兜底之前:
|
||||||
|
recipe = 查产出=stack 的配方
|
||||||
|
if recipe != null:
|
||||||
|
原料要素 = 递归 getObjectTags(原料)(深度 ≤2,visited 防环)
|
||||||
|
返回 原料要素之和(同种要素取最大,避免累加爆炸)
|
||||||
|
```
|
||||||
|
|
||||||
|
可选覆盖配方类型(按需):普通合成(Crafting)、熔炉(Smelting)、注魔(Infusion)、炼金(Crucible)、奥术(Arcane)。
|
||||||
|
|
||||||
|
### B. 性能:产出→配方索引
|
||||||
|
|
||||||
|
```
|
||||||
|
懒构建 Map<Item, List<Recipe>>(按产出分组,首次查询时建一次)
|
||||||
|
getObjectTags 内 O(1) 查索引,不再每次穷举遍历全部配方
|
||||||
|
结果缓存 ConcurrentHashMap<Item, AspectList>,绑定 Server 引用(/reload 自动失效)
|
||||||
|
```
|
||||||
|
|
||||||
|
### C. 要素限制(贴近 1.7.10 + 平衡)
|
||||||
|
|
||||||
|
- 单要素 ≤64
|
||||||
|
- 种类 ≤6(按数量降序取前 6)
|
||||||
|
- 总量按复杂度归一化:`clamp(8 + 原料种类×2, 8, 24)`
|
||||||
|
- 同种要素多个原料出现时**取最大值**(不累加,防 1000+)
|
||||||
|
|
||||||
|
## 复现步骤 Steps to Reproduce
|
||||||
|
|
||||||
|
1. 安装任意含合成配方的 mod(如 waystones 传送石碑:3 黑曜石 + 3 石砖 + 1 传送石)
|
||||||
|
2. 用魔导透镜扫描该 mod 物品
|
||||||
|
3. 实际:`ENTROPY×1` 或词根 1-3 种(传送石碑给 1 混沌)
|
||||||
|
4. 期望:黑曜石(暗/虚空)+ 石砖(石头)+ 传送石(水晶/末影)要素
|
||||||
|
|
||||||
|
## 性能复现
|
||||||
|
|
||||||
|
1. 客户端启动(JEI 加载,遍历全部物品调 getObjectTags)
|
||||||
|
2. F3+L 采样 10 秒
|
||||||
|
3. 实际:`getObjectTags` 占比极高(穷举遍历配方),单物品 10s 级
|
||||||
|
|
||||||
|
## 参考实现要点(作者可参考)
|
||||||
|
|
||||||
|
- 1.21.1 `RecipeManager.byType` 为 **private**——用 `getAllRecipesFor(RecipeType)`(公开,返回 `List<RecipeHolder<T>>`)
|
||||||
|
- `RecipeHolder.id()` 直接返回 `ResourceLocation`(无 `.location()`)
|
||||||
|
- TC 配方访问:`ThaumcraftApi.getInfusionRecipe(result)` / `getCrucibleRecipe(result)` / `getCraftingRecipes()`(遍历 `IArcaneRecipe`,含 `getAspects()` vis 成本)
|
||||||
|
- `InfusionRecipe` 原料:`centralInput()` + `getComponents()`(公开)
|
||||||
|
- 缓存注意:组件敏感物品(药水/染色/耐久 NBT 变体)不进缓存(TC 的 NBT 分支先截走);COMPUTING 哨兵防跨配方递归环;缓存绑定 Server 引用(reload 失效)
|
||||||
|
|
||||||
|
## 环境 Environment
|
||||||
|
|
||||||
|
- Minecraft 1.21.1
|
||||||
|
- NeoForge 21.1.x
|
||||||
|
- Thaumcraft
|
||||||
|
|
||||||
|
## 补充
|
||||||
|
|
||||||
|
- 完整反编译调研与参考实现已由 ThaumicEnergistics 移植团队完成,可提供更详细方案(含存储体系:内存缓存 + 可选本地二进制文件缓存,指纹校验跨启动复用)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
This is a community‑port of ThaumicEnergistics.
|
||||||
|
|
||||||
|
Modifications and port code: Copyright (c) 2026 AlkSur, Licensed under GNU LGPL‑3.0.
|
||||||
|
|
||||||
|
Original source code: Copyright (c) Chris, BrockWS, Licensed under MIT License (see LICENSE‑ORIGINAL‑MIT).
|
||||||
|
|
||||||
|
Original License:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018-2019 Chris, BrockWS
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -6,6 +6,7 @@ mod_group=thaumicenergistics
|
|||||||
mod_name=ThaumicEnergistics
|
mod_name=ThaumicEnergistics
|
||||||
mod_id=thaumicenergistics
|
mod_id=thaumicenergistics
|
||||||
mod_author=Nividica & Community
|
mod_author=Nividica & Community
|
||||||
|
modLicense=LGPL-3.0
|
||||||
|
|
||||||
# Shared (must match root gradle.properties)
|
# Shared (must match root gradle.properties)
|
||||||
minecraft_version=1.21.1
|
minecraft_version=1.21.1
|
||||||
|
|||||||
@@ -214,5 +214,10 @@ public final class ThaumicEnergistics {
|
|||||||
AECapabilities.IN_WORLD_GRID_NODE_HOST,
|
AECapabilities.IN_WORLD_GRID_NODE_HOST,
|
||||||
ModBlockEntities.ARCANE_ASSEMBLER.get(),
|
ModBlockEntities.ARCANE_ASSEMBLER.get(),
|
||||||
(be, context) -> (appeng.api.networking.IInWorldGridNodeHost) be);
|
(be, context) -> (appeng.api.networking.IInWorldGridNodeHost) be);
|
||||||
|
// 注魔监视器:连接 AE 网络(network 状态)需要网格节点 capability
|
||||||
|
event.registerBlockEntity(
|
||||||
|
AECapabilities.IN_WORLD_GRID_NODE_HOST,
|
||||||
|
ModBlockEntities.INFUSION_MONITOR.get(),
|
||||||
|
(be, context) -> (appeng.api.networking.IInWorldGridNodeHost) be);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ public final class RecipeRegistration {
|
|||||||
public static InfusionRecipe ESSENTIA_PROVIDER;
|
public static InfusionRecipe ESSENTIA_PROVIDER;
|
||||||
public static InfusionRecipe INFUSION_PROVIDER;
|
public static InfusionRecipe INFUSION_PROVIDER;
|
||||||
public static InfusionRecipe ARCANE_ASSEMBLER;
|
public static InfusionRecipe ARCANE_ASSEMBLER;
|
||||||
|
/** 韵魔观测器注魔配方。 */
|
||||||
|
public static InfusionRecipe INFUSION_MONITOR;
|
||||||
// 奥术有序配方
|
// 奥术有序配方
|
||||||
public static ShapedArcaneRecipe STORAGE_COMPONENT_1K;
|
public static ShapedArcaneRecipe STORAGE_COMPONENT_1K;
|
||||||
public static ShapedArcaneRecipe STORAGE_COMPONENT_4K;
|
public static ShapedArcaneRecipe STORAGE_COMPONENT_4K;
|
||||||
@@ -310,6 +312,19 @@ public final class RecipeRegistration {
|
|||||||
tc("earth_shard"),
|
tc("earth_shard"),
|
||||||
tc("air_shard"),
|
tc("air_shard"),
|
||||||
createFullVisScepter());
|
createFullVisScepter());
|
||||||
|
|
||||||
|
// 4. 韵魔观测器
|
||||||
|
// 中央:原版侦测器;外围:元始护符 + 揭示护目镜 + 世界盐×2。
|
||||||
|
// 需要灵气:魔力×16、灵气(AURA)×32、感官(SENSES, tag=sensus)×16。
|
||||||
|
// 不稳定度 3(配方基础,参与"总不稳定 = 基础 + 祭坛对称度",可自由调整:
|
||||||
|
// 1~2 低风险 / 3~5 中等 / 6~8 高)。
|
||||||
|
INFUSION_MONITOR = ThaumcraftApi.addInfusionCraftingRecipe("INFUSIONMONITOR", stack(ModBlocks.INFUSION_MONITOR),
|
||||||
|
3, new AspectList().add(Aspect.MAGIC, 16).add(Aspect.AURA, 32).add(Aspect.SENSES, 16),
|
||||||
|
new ItemStack(Items.OBSERVER),
|
||||||
|
tc("primal_charm"),
|
||||||
|
tc("goggles_revealing"),
|
||||||
|
tc("salis_mundus"),
|
||||||
|
tc("salis_mundus"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public final class ThEThaumcraft {
|
|||||||
registerVisInterface();
|
registerVisInterface();
|
||||||
registerEssentiaBuses();
|
registerEssentiaBuses();
|
||||||
registerInfusionProvider();
|
registerInfusionProvider();
|
||||||
|
registerInfusionMonitor();
|
||||||
registerEssentiaProvider();
|
registerEssentiaProvider();
|
||||||
registerEssentiaTerminal();
|
registerEssentiaTerminal();
|
||||||
registerDigisentia();
|
registerDigisentia();
|
||||||
@@ -259,6 +260,20 @@ public final class ThEThaumcraft {
|
|||||||
.registerResearchItem();
|
.registerResearchItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 8. (-7,-2) INFUSIONMONITOR 韵魔观测器====================
|
||||||
|
// 前置: INFUSIONPROVIDER(注魔供应器);位于其左侧。无需研究比较,直接花费要素解锁。
|
||||||
|
private static void registerInfusionMonitor() {
|
||||||
|
new ResearchItem("INFUSIONMONITOR", TAB,
|
||||||
|
new AspectList().add(Aspect.MAGIC, 4).add(Aspect.AURA, 4).add(Aspect.SENSES, 6),
|
||||||
|
-7, -2, 1, new ItemStack(ModItems.INFUSION_MONITOR_ITEM.get()))
|
||||||
|
.setRound().setParents("INFUSIONPROVIDER")
|
||||||
|
.setPages(
|
||||||
|
new ResearchPage("tc.research_text.INFUSIONMONITOR.stage.1"),
|
||||||
|
new ResearchPage(RecipeRegistration.INFUSION_MONITOR),
|
||||||
|
new ResearchPage("tc.research_text.INFUSIONMONITOR.stage.2"))
|
||||||
|
.registerResearchItem();
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 8. (-2,-4) ESSENTIAPROVIDER 源质供应器====================
|
// ==================== 8. (-2,-4) ESSENTIAPROVIDER 源质供应器====================
|
||||||
// 前置: ESSENTIABUSES(源质总线)
|
// 前置: ESSENTIABUSES(源质总线)
|
||||||
private static void registerEssentiaProvider() {
|
private static void registerEssentiaProvider() {
|
||||||
|
|||||||
@@ -46,10 +46,13 @@ public record EssentiaTerminalFillC2SPacket(int containerId, ResourceLocation as
|
|||||||
&& ctx.player().containerMenu instanceof appeng.menu.me.common.MEStorageMenu menu
|
&& ctx.player().containerMenu instanceof appeng.menu.me.common.MEStorageMenu menu
|
||||||
&& menu instanceof thaumicenergistics.common.container.ThEEssentiaMenuAccess acc) {
|
&& menu instanceof thaumicenergistics.common.container.ThEEssentiaMenuAccess acc) {
|
||||||
var player = menu.getPlayerInventory().player;
|
var player = menu.getPlayerInventory().player;
|
||||||
// 源质物品:优先包携带的 sourceStack(客户端真实来源);否则回退菜单 carried/主手
|
// ⚠ 必须用玩家真实栈(carried/主手)而非包里的拷贝:装罐会原地消耗空安瓿(shrink)
|
||||||
var source = !this.sourceStack.isEmpty() ? this.sourceStack
|
// 或修改罐子内容(setAspects)。若用 sourceStack(拷贝),消耗/修改不落真实物品——
|
||||||
: (!menu.getCarried().isEmpty() ? menu.getCarried()
|
// 空安瓿不消耗却能凭空得到满安瓿。
|
||||||
: player.getMainHandItem());
|
// 顺序与客户端一致:优先 carried(拾取),其次主手;sourceStack 仅兜底。
|
||||||
|
var source = !menu.getCarried().isEmpty() ? menu.getCarried()
|
||||||
|
: (!player.getMainHandItem().isEmpty() ? player.getMainHandItem()
|
||||||
|
: this.sourceStack);
|
||||||
thaumicenergistics.common.container.EssentiaFillHelper.fillContainerFromGrid(
|
thaumicenergistics.common.container.EssentiaFillHelper.fillContainerFromGrid(
|
||||||
acc.thE_getStorage(), acc.thE_getEnergySource(),
|
acc.thE_getStorage(), acc.thE_getEnergySource(),
|
||||||
menu.getActionSource(), player, source, this.aspectId);
|
menu.getActionSource(), player, source, this.aspectId);
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ public final class ModBlockEntities {
|
|||||||
REGISTRY.register("essentia_cell_workbench",
|
REGISTRY.register("essentia_cell_workbench",
|
||||||
() -> BlockEntityType.Builder.of(TileEssentiaCellWorkbench::new, ModBlocks.ESSENTIA_CELL_WORKBENCH.get()).build(null));
|
() -> BlockEntityType.Builder.of(TileEssentiaCellWorkbench::new, ModBlocks.ESSENTIA_CELL_WORKBENCH.get()).build(null));
|
||||||
|
|
||||||
|
public static final Supplier<BlockEntityType<TileInfusionMonitor>> INFUSION_MONITOR =
|
||||||
|
REGISTRY.register("infusion_monitor",
|
||||||
|
() -> BlockEntityType.Builder.of(TileInfusionMonitor::new, ModBlocks.INFUSION_MONITOR.get()).build(null));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 虚拟的 Thaumcraft vis 源,供 vis 接口的提供端使用。
|
* 虚拟的 Thaumcraft vis 源,供 vis 接口的提供端使用。
|
||||||
* 不作为真实方块放置;由 {@link thaumicenergistics.common.parts.VisInterfacePart}
|
* 不作为真实方块放置;由 {@link thaumicenergistics.common.parts.VisInterfacePart}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import net.minecraft.world.level.block.SoundType;
|
|||||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.material.MapColor;
|
import net.minecraft.world.level.material.MapColor;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.neoforged.neoforge.registries.DeferredBlock;
|
import net.neoforged.neoforge.registries.DeferredBlock;
|
||||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||||
import thaumicenergistics.ThaumicEnergistics;
|
import thaumicenergistics.ThaumicEnergistics;
|
||||||
import thaumicenergistics.common.blocks.OrientableEntityBlock;
|
import thaumicenergistics.common.blocks.OrientableEntityBlock;
|
||||||
import thaumicenergistics.common.blocks.ThEBaseEntityBlock;
|
import thaumicenergistics.common.blocks.ThEBaseEntityBlock;
|
||||||
import thaumicenergistics.common.tiles.*;
|
import thaumicenergistics.common.tiles.*;
|
||||||
|
import thaumicenergistics.init.ModItems;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public final class ModBlocks {
|
public final class ModBlocks {
|
||||||
@@ -119,4 +121,180 @@ public final class ModBlocks {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================== 注魔监视器(灵气观测器) ====================
|
||||||
|
|
||||||
|
/** 是否放入魔导手册。 */
|
||||||
|
public static final net.minecraft.world.level.block.state.properties.BooleanProperty BOOK =
|
||||||
|
net.minecraft.world.level.block.state.properties.BooleanProperty.create("book");
|
||||||
|
/** 是否连接 AE 网络。 */
|
||||||
|
public static final net.minecraft.world.level.block.state.properties.BooleanProperty NETWORK =
|
||||||
|
net.minecraft.world.level.block.state.properties.BooleanProperty.create("network");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注魔监视器:4 态(off / off+book / off+book+on / off+Jade),blockstate 用 multipart 合并模型。
|
||||||
|
*/
|
||||||
|
public static final DeferredBlock<Block> INFUSION_MONITOR = REGISTRY.register("infusion_monitor", () -> new ThEBaseEntityBlock(P) {
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
return new TileInfusionMonitor(pos, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends net.minecraft.world.level.block.entity.BlockEntity> net.minecraft.world.level.block.entity.BlockEntityTicker<T> getTicker(net.minecraft.world.level.Level level, BlockState state, net.minecraft.world.level.block.entity.BlockEntityType<T> type) {
|
||||||
|
if (level.isClientSide()) return null;
|
||||||
|
if (type == ModBlockEntities.INFUSION_MONITOR.get()) {
|
||||||
|
return (lvl, pos, st, be) -> TileInfusionMonitor.tick(lvl, pos, st, (TileInfusionMonitor) be);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createBlockStateDefinition(net.minecraft.world.level.block.state.StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
|
builder.add(BOOK, NETWORK, FACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
|
||||||
|
// 放置时始终朝向放置者
|
||||||
|
return this.defaultBlockState()
|
||||||
|
.setValue(FACING, context.getHorizontalDirection().getOpposite())
|
||||||
|
.setValue(BOOK, false)
|
||||||
|
.setValue(NETWORK, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected net.minecraft.world.ItemInteractionResult useItemOn(net.minecraft.world.item.ItemStack stack, BlockState state, net.minecraft.world.level.Level level, BlockPos pos, net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand, net.minecraft.world.phys.BlockHitResult hit) {
|
||||||
|
if (level.getBlockEntity(pos) instanceof TileInfusionMonitor monitor) {
|
||||||
|
// 手持魔导手册 → 直接放入(返回 SUCCESS 拦截,避免魔导手册自身 use 打开界面)
|
||||||
|
if (TileInfusionMonitor.isThaumonomicon(stack)) {
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
monitor.placeBook(player, stack);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
// Shift+右键(非手册物品)→ 取回手册
|
||||||
|
if (player.isShiftKeyDown()) {
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
monitor.retrieveBook(player);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
// 普通右键(非手册/非Shift)→ 聊天框显示风险描述
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
monitor.showDescriptionInChat(player);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
return net.minecraft.world.ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空手右键(1.21 走 useWithoutItem):Shift+空手 → 取回手册;普通空手右键 → 聊天框显示描述
|
||||||
|
@Override
|
||||||
|
protected net.minecraft.world.InteractionResult useWithoutItem(BlockState state, net.minecraft.world.level.Level level, BlockPos pos, net.minecraft.world.entity.player.Player player, net.minecraft.world.phys.BlockHitResult hit) {
|
||||||
|
if (level.getBlockEntity(pos) instanceof TileInfusionMonitor monitor) {
|
||||||
|
if (player.isShiftKeyDown()) {
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
monitor.retrieveBook(player);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.InteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
monitor.showDescriptionInChat(player);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.InteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
return net.minecraft.world.InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.phys.shapes.VoxelShape getShape(BlockState state, net.minecraft.world.level.BlockGetter level, BlockPos pos, net.minecraft.world.phys.shapes.CollisionContext context) {
|
||||||
|
// 底座(高 0.25)+ 上部塔架(0.25~0.8)
|
||||||
|
return net.minecraft.world.phys.shapes.Shapes.or(
|
||||||
|
net.minecraft.world.phys.shapes.Shapes.box(0.0, 0.0, 0.0, 1.0, 0.25, 1.0),
|
||||||
|
net.minecraft.world.phys.shapes.Shapes.box(0.15, 0.25, 0.15, 0.85, 0.8, 0.85));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Alkusure86 玩偶(Fumo) ====================
|
||||||
|
|
||||||
|
/** 水平朝向(韵魔观测器共用)。 */
|
||||||
|
public static final net.minecraft.world.level.block.state.properties.DirectionProperty FACING =
|
||||||
|
net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING;
|
||||||
|
|
||||||
|
// 姿态变体:0 = alkusure86fumo(默认坐姿),1 = a999csfumo。
|
||||||
|
public static final net.minecraft.world.level.block.state.properties.BooleanProperty VARIANT =
|
||||||
|
net.minecraft.world.level.block.state.properties.BooleanProperty.create("variant");
|
||||||
|
|
||||||
|
// 玩偶专用:低硬度 → 空手可快速挖掘(装饰物)。
|
||||||
|
private static final BlockBehaviour.Properties PD =
|
||||||
|
BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_PINK).strength(0.2f).sound(SoundType.WOOL);
|
||||||
|
|
||||||
|
// Alkusure86 玩偶:纯装饰方块,2 个姿态模型 + 水平朝向。
|
||||||
|
// 右键切换姿态;Shift+右键拿起方块到手。
|
||||||
|
public static final DeferredBlock<Block> ALKUSURE86_FUMO = REGISTRY.register("alkusure86fumo", () -> new Block(PD) {
|
||||||
|
@Override
|
||||||
|
protected void createBlockStateDefinition(net.minecraft.world.level.block.state.StateDefinition.Builder<Block, BlockState> builder) {
|
||||||
|
builder.add(FACING, VARIANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
|
||||||
|
// 默认坐姿(variant=0),面向放置者
|
||||||
|
return this.defaultBlockState()
|
||||||
|
.setValue(FACING, context.getHorizontalDirection().getOpposite())
|
||||||
|
.setValue(VARIANT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected net.minecraft.world.ItemInteractionResult useItemOn(net.minecraft.world.item.ItemStack stack, BlockState state, net.minecraft.world.level.Level level, BlockPos pos, net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand, net.minecraft.world.phys.BlockHitResult hit) {
|
||||||
|
if (player.isShiftKeyDown()) {
|
||||||
|
// Shift+右键:拿起方块到手(或背包)
|
||||||
|
pickUp(level, pos, player);
|
||||||
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
// 右键:切换姿态
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
level.setBlock(pos, state.setValue(VARIANT, !state.getValue(VARIANT)), 3);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.ItemInteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空手右键(1.21 走 useWithoutItem):Shift+空手拿起;右键切换姿态
|
||||||
|
@Override
|
||||||
|
protected net.minecraft.world.InteractionResult useWithoutItem(BlockState state, net.minecraft.world.level.Level level, BlockPos pos, net.minecraft.world.entity.player.Player player, net.minecraft.world.phys.BlockHitResult hit) {
|
||||||
|
if (player.isShiftKeyDown()) {
|
||||||
|
pickUp(level, pos, player);
|
||||||
|
return net.minecraft.world.InteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
if (!level.isClientSide) {
|
||||||
|
level.setBlock(pos, state.setValue(VARIANT, !state.getValue(VARIANT)), 3);
|
||||||
|
}
|
||||||
|
return net.minecraft.world.InteractionResult.sidedSuccess(level.isClientSide());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift+右键拿起:移除方块,物品进主手(空手时)或背包。
|
||||||
|
private void pickUp(net.minecraft.world.level.Level level, BlockPos pos, net.minecraft.world.entity.player.Player player) {
|
||||||
|
if (level.isClientSide) return;
|
||||||
|
ItemStack item = new ItemStack(ModItems.ALKUSURE86_FUMO_ITEM.get());
|
||||||
|
if (player.getMainHandItem().isEmpty()) {
|
||||||
|
player.setItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND, item);
|
||||||
|
} else if (!player.getInventory().add(item)) {
|
||||||
|
net.minecraft.world.Containers.dropItemStack(level, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, item);
|
||||||
|
}
|
||||||
|
level.removeBlock(pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public net.minecraft.world.phys.shapes.VoxelShape getShape(BlockState state, net.minecraft.world.level.BlockGetter level, BlockPos pos, net.minecraft.world.phys.shapes.CollisionContext context) {
|
||||||
|
// 玩偶整体占位:坐姿高度约 12 格单位(0.75 格),宽约 8(0.5 格)
|
||||||
|
return net.minecraft.world.phys.shapes.Shapes.box(0.25, 0.0, 0.25, 0.75, 0.75, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 纯 Block 无战利品表 → 必须覆写 getDrops,否则挖掘不掉落
|
||||||
|
@Override
|
||||||
|
public java.util.List<ItemStack> getDrops(BlockState state, net.minecraft.world.level.storage.loot.LootParams.Builder builder) {
|
||||||
|
return java.util.List.of(new ItemStack(ModItems.ALKUSURE86_FUMO_ITEM.get()));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public final class ModCreativeTab {
|
|||||||
output.accept(ModItems.ARCANE_ASSEMBLER_ITEM.get()); output.accept(ModItems.ESSENTIA_VIBRATION_CHAMBER_ITEM.get());
|
output.accept(ModItems.ARCANE_ASSEMBLER_ITEM.get()); output.accept(ModItems.ESSENTIA_VIBRATION_CHAMBER_ITEM.get());
|
||||||
output.accept(ModItems.KNOWLEDGE_INSCRIBER_ITEM.get()); output.accept(ModItems.ESSENTIA_CELL_WORKBENCH_ITEM.get());
|
output.accept(ModItems.KNOWLEDGE_INSCRIBER_ITEM.get()); output.accept(ModItems.ESSENTIA_CELL_WORKBENCH_ITEM.get());
|
||||||
output.accept(ModItems.DISTILLATION_ENCODER_ITEM.get());
|
output.accept(ModItems.DISTILLATION_ENCODER_ITEM.get());
|
||||||
|
output.accept(ModItems.INFUSION_MONITOR_ITEM.get());
|
||||||
|
// 玩偶不进创造物品栏:需 /give @p thaumicenergistics:alkusure86fumo 手动获取
|
||||||
|
// output.accept(ModItems.ALKUSURE86_FUMO_ITEM.get());
|
||||||
|
|
||||||
// 源质虚拟物品不再在创造标签页中注册(避免玩家拿到物品栏)
|
// 源质虚拟物品不再在创造标签页中注册(避免玩家拿到物品栏)
|
||||||
// 它们通过 JEI 插件注册,仅作为 ghost ingredient 可拖拽到 AE 筛选槽
|
// 它们通过 JEI 插件注册,仅作为 ghost ingredient 可拖拽到 AE 筛选槽
|
||||||
|
|||||||
@@ -78,4 +78,6 @@ public final class ModItems {
|
|||||||
public static final DeferredItem<BlockItem> KNOWLEDGE_INSCRIBER_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.KNOWLEDGE_INSCRIBER);
|
public static final DeferredItem<BlockItem> KNOWLEDGE_INSCRIBER_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.KNOWLEDGE_INSCRIBER);
|
||||||
public static final DeferredItem<BlockItem> ESSENTIA_CELL_WORKBENCH_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.ESSENTIA_CELL_WORKBENCH);
|
public static final DeferredItem<BlockItem> ESSENTIA_CELL_WORKBENCH_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.ESSENTIA_CELL_WORKBENCH);
|
||||||
public static final DeferredItem<BlockItem> DISTILLATION_ENCODER_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.DISTILLATION_ENCODER);
|
public static final DeferredItem<BlockItem> DISTILLATION_ENCODER_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.DISTILLATION_ENCODER);
|
||||||
|
public static final DeferredItem<BlockItem> INFUSION_MONITOR_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.INFUSION_MONITOR);
|
||||||
|
public static final DeferredItem<BlockItem> ALKUSURE86_FUMO_ITEM = REGISTRY.registerSimpleBlockItem(ModBlocks.ALKUSURE86_FUMO);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"itemGroup.thaumicenergistics": "Thaumic Energistics",
|
"itemGroup.thaumicenergistics": "Thaumic Energistics",
|
||||||
"block.thaumicenergistics.essentia_provider": "Essentia Provider",
|
"block.thaumicenergistics.essentia_provider": "Essentia Provider",
|
||||||
"block.thaumicenergistics.infusion_provider": "Infusion Provider",
|
"block.thaumicenergistics.infusion_provider": "Infusion Provider",
|
||||||
@@ -102,6 +102,8 @@
|
|||||||
"tc.research_name.PSEUDO_SCEPTRE": "Crafting Scepters",
|
"tc.research_name.PSEUDO_SCEPTRE": "Crafting Scepters",
|
||||||
"tc.research_name.PSEUDO_COREGATHER": "Golem Core: Gather",
|
"tc.research_name.PSEUDO_COREGATHER": "Golem Core: Gather",
|
||||||
|
|
||||||
|
"gui.thaumicenergistics.ArcaneCraftingTerminal": "Arcane Crafting Terminal",
|
||||||
|
"thaumicenergistics.jade.current_aspect": "Fuel: %s",
|
||||||
"thaumicenergistics.jade.suction_type": "Suction: %s",
|
"thaumicenergistics.jade.suction_type": "Suction: %s",
|
||||||
"thaumicenergistics.jade.essentia_stored": "Essentia: %d / %d",
|
"thaumicenergistics.jade.essentia_stored": "Essentia: %d / %d",
|
||||||
"thaumicenergistics.jade.burning": "Burning %d%%, %.1f AE/tick",
|
"thaumicenergistics.jade.burning": "Burning %d%%, %.1f AE/tick",
|
||||||
@@ -114,5 +116,18 @@
|
|||||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "Not scanned",
|
"thaumicenergistics.gui.distillation_encoder.not_scanned": "Not scanned",
|
||||||
"thaumicenergistics.jei.transfer.missing": "Missing: %d",
|
"thaumicenergistics.jei.transfer.missing": "Missing: %d",
|
||||||
"thaumicenergistics.jei.transfer.craftable": "Craftable: %d",
|
"thaumicenergistics.jei.transfer.craftable": "Craftable: %d",
|
||||||
"thaumicenergistics.jei.transfer.ctrl_click": "Use Ctrl to craft missing items"
|
"thaumicenergistics.jei.transfer.ctrl_click": "Use Ctrl to craft missing items",
|
||||||
|
"block.thaumicenergistics.infusion_monitor": "Infusion Monitor",
|
||||||
|
"thaumicenergistics.jade.monitor.title": "Infusion Monitor",
|
||||||
|
"thaumicenergistics.jade.monitor.no_matrix": "No infusion altar detected",
|
||||||
|
"thaumicenergistics.jade.monitor.instability": "Instability: %s (base %s + altar %s)",
|
||||||
|
"thaumicenergistics.jade.monitor.crafting": "Infusion in progress",
|
||||||
|
"thaumicenergistics.jade.monitor.shortages": "Essentia shortage: %s",
|
||||||
|
"thaumicenergistics.jade.monitor.essence_ok": "Essentia sufficient",
|
||||||
|
"block.thaumicenergistics.alkusure86fumo": "Alkusure86 Fumo",
|
||||||
|
"tc.research_text.INFUSIONMONITOR.stage.1": "To observe is to command. You place the Thaumonomicon beside the altar, an observer peering into each pulse of the infusion — the stirring of aura, the opening of rifts, the first whisper of flux. Bound to an ME network, the observer truly opens its eyes.",
|
||||||
|
"tc.research_text.INFUSIONMONITOR.stage.2": "The Infusion Monitor takes the Thaumonomicon as its key and the network as its sight. Place the tome and connect to the network, and it shall hover above the void, truthfully telling the risk of the ritual — and on right-click, it whispers its deeper warnings.",
|
||||||
|
|
||||||
|
"tc.research_name.INFUSIONMONITOR": "Infusion Monitor",
|
||||||
|
"tc.research_name.GOLEMWIFIBACKPACK": "Golem WiFi Backpack"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"itemGroup.thaumicenergistics": "神秘能源",
|
"itemGroup.thaumicenergistics": "神秘能源",
|
||||||
"block.thaumicenergistics.essentia_provider": "源质供应器",
|
"block.thaumicenergistics.essentia_provider": "源质供应器",
|
||||||
"block.thaumicenergistics.infusion_provider": "注魔供应器",
|
"block.thaumicenergistics.infusion_provider": "注魔供应器",
|
||||||
@@ -133,5 +133,17 @@
|
|||||||
"thaumicenergistics.gui.distillation_encoder.not_scanned": "未扫描",
|
"thaumicenergistics.gui.distillation_encoder.not_scanned": "未扫描",
|
||||||
"thaumicenergistics.jei.transfer.missing": "缺少: %d",
|
"thaumicenergistics.jei.transfer.missing": "缺少: %d",
|
||||||
"thaumicenergistics.jei.transfer.craftable": "可合成: %d",
|
"thaumicenergistics.jei.transfer.craftable": "可合成: %d",
|
||||||
"thaumicenergistics.jei.transfer.ctrl_click": "按住 Ctrl 合成缺失物品"
|
"thaumicenergistics.jei.transfer.ctrl_click": "按住 Ctrl 合成缺失物品",
|
||||||
|
"block.thaumicenergistics.infusion_monitor": "韵魔观测器",
|
||||||
|
"thaumicenergistics.jade.monitor.title": "韵魔观测器",
|
||||||
|
"thaumicenergistics.jade.monitor.no_matrix": "未检测到注魔祭坛",
|
||||||
|
"thaumicenergistics.jade.monitor.instability": "不稳定度: %s (基础 %s + 祭坛 %s)",
|
||||||
|
"thaumicenergistics.jade.monitor.crafting": "注魔进行中",
|
||||||
|
"thaumicenergistics.jade.monitor.shortages": "源质缺口: %s",
|
||||||
|
"thaumicenergistics.jade.monitor.essence_ok": "源质充足",
|
||||||
|
"block.thaumicenergistics.alkusure86fumo": "Alkusure86 玩偶",
|
||||||
|
"tc.research_text.INFUSIONMONITOR.stage.1": "观测即是驾驭。你将魔导手册置于祭坛之侧,以侦测器窥探注魔的每一次脉动——灵光、裂隙、咒波的先兆,尽收眼底。当它与 ME 网络相连,观测者便真正睁开眼。",
|
||||||
|
"tc.research_text.INFUSIONMONITOR.stage.2": "韵魔观测器以魔导手册为钥、以网络为眼。放上手册并接入网络,它便会悬浮于虚空之中,如实告知你注魔之仪的风险——而按住右键,它将低语出更深层的告诫。",
|
||||||
|
"tc.research_name.INFUSIONMONITOR": "韵魔观测器",
|
||||||
|
"tc.research_name.GOLEMWIFIBACKPACK": "傀儡WiFi背包"
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
"thaumicenergistics:essentia_vibration_chamber",
|
"thaumicenergistics:essentia_vibration_chamber",
|
||||||
"thaumicenergistics:knowledge_inscriber",
|
"thaumicenergistics:knowledge_inscriber",
|
||||||
"thaumicenergistics:essentia_cell_workbench",
|
"thaumicenergistics:essentia_cell_workbench",
|
||||||
"thaumicenergistics:distillation_encoder"
|
"thaumicenergistics:distillation_encoder",
|
||||||
|
"thaumicenergistics:infusion_monitor",
|
||||||
|
"thaumicenergistics:alkusure86fumo"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# ThaumicEnergistics 代码位置速查表
|
||||||
|
|
||||||
|
> 说明:本文件所有路径均相对于 `modules/thaumicenergistics-neo/src/main/`。
|
||||||
|
> 若文件路径以 `java/` 开头 → Java 源码;以 `resources/assets/` 开头 → 资源/JSON。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、物品 / 方块 Java 注册
|
||||||
|
|
||||||
|
| 你想找什么 | 文件 | 关键行 | 备注 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 物品总注册器入口 | `java/thaumicenergistics/init/ModItems.java` | **12** | `DeferredRegister.Items REGISTRY` |
|
||||||
|
| 1k/4k/16k/64k 源质存储单元(带容量的 Cell) | `ModItems.java` | 14~18 | `ESSENTIA_CELL_1K ~ ESSENTIA_CELL_CREATIVE` |
|
||||||
|
| 1k/4k/16k/64k 存储组件(Storage Component) | `ModItems.java` | 19~22 | `STORAGE_COMPONENT_1K ~ 64K` |
|
||||||
|
| 聚变核心 / 扩散核心 | `ModItems.java` | 23~24 | `COALESCENCE_CORE` / `DIFFUSION_CORE` |
|
||||||
|
| 铁齿轮 / 知识核心 / AE扳手核心 | `ModItems.java` | 25~27 | `IRON_GEAR` / `KNOWLEDGE_CORE` / `FOCUS_AEWRENCH` |
|
||||||
|
| 无线源质终端 / 傀儡WiFi背包 | `ModItems.java` | 28~29 | `WIRELESS_ESSENTIA_TERMINAL` / `GOLEM_WIFI_BACKPACK` |
|
||||||
|
| 源质IO总线/存储总线/终端/LevelEmitter/Vis接口/共鸣器 | `ModItems.java` | 30~37 | `ESSENTIA_IMPORT_BUS` 等一整套 |
|
||||||
|
| 方块对应物品(BlockItem) | `ModItems.java` | 39~47 | `ESSENTIA_PROVIDER_ITEM`、`KNOWLEDGE_INSCRIBER_ITEM` 等,与 ModBlocks 一一对应 |
|
||||||
|
| 方块总注册器入口 | `java/thaumicenergistics/init/ModBlocks.java` | **17** | `DeferredRegister.Blocks REGISTRY` |
|
||||||
|
| 源质提供器 Essentia Provider | `ModBlocks.java` | 20 | `ESSENTIA_PROVIDER` |
|
||||||
|
| 注入提供器 Infusion Provider | `ModBlocks.java` | 21 | `INFUSION_PROVIDER` |
|
||||||
|
| 奥术组装机 Arcane Assembler | `ModBlocks.java` | 22 | `ARCANE_ASSEMBLER`(Tile: TileArcaneAssembler) |
|
||||||
|
| 源质振动室 Essentia Vibration Chamber | `ModBlocks.java` | 23 | `ESSENTIA_VIBRATION_CHAMBER`(Tile: TileEssentiaVibrationChamber) |
|
||||||
|
| 知识刻印机 Knowledge Inscriber | `ModBlocks.java` | 24 | `KNOWLEDGE_INSCRIBER`(纯方块,无BE) |
|
||||||
|
| 精馏编码器 Distillation Encoder | `ModBlocks.java` | 25 | `DISTILLATION_ENCODER`(纯方块,无BE) |
|
||||||
|
| 齿轮箱 / 傀儡齿轮箱 | `ModBlocks.java` | 26~27 | `GEAR_BOX` / `GOLEM_GEAR_BOX` |
|
||||||
|
| 源质单元工作台 Essentia Cell Workbench(含 GUI 打开逻辑) | `ModBlocks.java` | 28~38 | 里面直接写了 `useItemOn` 和 `getMenuProvider` |
|
||||||
|
| 方块实体(BlockEntity)注册器 | `java/thaumicenergistics/init/ModBlockEntities.java` | — | (与 ModBlocks 里带 Tile 的对应) |
|
||||||
|
| 菜单类型 / 屏幕注册 | `java/thaumicenergistics/init/ModMenuTypes.java` + `ThaumicEnergistics.java:40~46` | — | `registerScreens` 方法里绑定 GuiXXX::new |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、配方注册(1k/4k/16k/64k 组件、奥术、注入、坩埚)
|
||||||
|
|
||||||
|
| 你想找什么 | 文件 | 关键行 | 备注 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 配方总入口(commonSetup 里触发) | `java/thaumicenergistics/ThaumicEnergistics.java` | **36~38** | `commonSetup(event) { event.enqueueWork(RecipeRegistration::registerAll); }` |
|
||||||
|
| 配方注册总开关 | `java/thaumicenergistics/common/features/RecipeRegistration.java` | **247~253** | `registerAll()` 调 4 个子方法 + 打日志 |
|
||||||
|
| 有形状奥术合成(1k/4k/16k/64k、齿轮箱、刻印机等 14 条) | `RecipeRegistration.java` | **255~417** | `registerShapedArcane()` |
|
||||||
|
| ⭐ 1k Storage Component(任意天域之华版,for 循环展开) | `RecipeRegistration.java` | 256~267 | `wisps1k.get(i)` 当 'E' |
|
||||||
|
| ⭐ 4k Storage Component(任意天域之华版) | `RecipeRegistration.java` | 269~281 | `wisps4k.get(i)` 当 'E' |
|
||||||
|
| ⭐ 16k Storage Component(任意天域之华版) | `RecipeRegistration.java` | 283~296 | `wisps16k.get(i)` 当 'E' |
|
||||||
|
| 64k Storage Component | `RecipeRegistration.java` | 298~307 | 单配方,不用循环 |
|
||||||
|
| 天域之华名称表 + wispEssenceStacks()(不用 Tag,硬编码 ID 绕过时序) | `RecipeRegistration.java` | **Tag 常量区 + WISP_ESSENCE_VARIANTS 数组**(第 227 行起) | 这是配方「任意天域之华」能生效的核心 |
|
||||||
|
| 无形状奥术合成(核心、总线、终端等 7 条) | `RecipeRegistration.java` | 419~473 | `registerShapelessArcane()` |
|
||||||
|
| 注入配方(Essentia/Infusion Provider、奥术组装机) | `RecipeRegistration.java` | 475~514 | `registerInfusion()` |
|
||||||
|
| 坩埚配方(傀儡齿轮箱 = 齿轮箱熔炼) | `RecipeRegistration.java` | 516~523 | `registerCrucible()` |
|
||||||
|
| 3 个 helper:`stack()` 物品封装 / `tc()` 取 Thaumcraft 物品 / `ae()` 取 AE2 物品 | `RecipeRegistration.java` | 525~537 | 配方里所有 `'X', ae(...)`、`'X', tc(...)` 都走这里 |
|
||||||
|
| 配方乱码前缀 | `RecipeRegistration.java` | 所有 `addXxxRecipe` 第一个参数 | `"th_a€‰"` / `"th_b€‰"` / `"th_c€‰"` / `"th_d€‰"`(不影响功能但建议后续统一换成 `R + "xxx"`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、资源引用链(材质 / 模型 / BlockState)
|
||||||
|
|
||||||
|
> 根目录:`resources/assets/thaumicenergistics/`
|
||||||
|
> 规则:**注册名 = 文件名(不含 .json/.png)**。物品/方块在 ModItems/ModBlocks 里用什么 `register("xxx")`,
|
||||||
|
> 下面 4 层 JSON/PNG 就必须都叫 `xxx`。
|
||||||
|
|
||||||
|
| 层 | 路径 | 它引用谁 | 关键字段 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ① BlockState(方块状态→方块模型) | `blockstates/<注册名>.json`(共 9 个) | 指向 `models/block/xxx.json` | `"model": "thaumicenergistics:block/<注册名>"` |
|
||||||
|
| ② Block Model(方块模型→PNG 贴图) | `models/block/<注册名>.json`(共 9 个) | 指向 `textures/block/xxx.png` | `"textures": { "up": "...", "down": "...", "north": "...", ... }` |
|
||||||
|
| ③ Item Model(物品模型→方块模型 **或** 物品 PNG) | `models/item/<注册名>.json`(物品 23 个 + 方块物品 9 个 = 32 个) | 方块物品:parent 到 ②;纯物品:直接写 layer0 | 方块物品:`"parent": "thaumicenergistics:block/<注册名>"`;纯物品:`"textures": { "layer0": "thaumicenergistics:item/<注册名>" }` |
|
||||||
|
| ④ PNG 贴图 | `textures/block/*.png`(9 套方块面)、`textures/item/*.png`(23 个物品贴图) | 被 ② ③ 引用 | 直接对应文件名 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、几个你最常问的具体定位(直接跳)
|
||||||
|
|
||||||
|
| 问题 | 去哪看 |
|
||||||
|
|---|---|
|
||||||
|
| 1k/4k/16k 组件配方「任意天域之华」怎么实现的 | `RecipeRegistration.java:227~244`(名称表 + `wispEssenceStacks()`) + `255~296`(for 循环注册) |
|
||||||
|
| 为什么要写死 `WISP_ESSENCE_VARIANTS` 数组,不能用 Tag 吗 | Tag 要到 DataPack 加载(比 commonSetup 晚)才填充,commonSetup 里 `getTag()` 返回空 → for 循环 0 次 → 配方直接没了。所以**注册阶段必须绕开 Tag 用硬编码 ID**。 |
|
||||||
|
| 「JEI 里完全没配方」排查顺序 | ① `latest.log` 里搜 `Wisp essence variants loaded` 看数字是不是 0;② 搜 `Invalid arcane recipe ingredient`(Ingredient 传进去就崩);③ 搜 `Mod loading has failed` 看 FMLCommonSetupEvent 是不是中途崩了;④ 确认 `commonSetup` 里是不是 `event.enqueueWork(RecipeRegistration::registerAll)` |
|
||||||
|
| Essentia Provider / Infusion Provider / Knowledge Inscriber / Gear Box「完全没材质」排查顺序 | 层 ①→②→③→④ 依次看:① blockstates 里 model 指向对不对;② models/block 里 textures 每个面都写了且 PNG 存在;③ models/item 里 parent 指回方块模型;④ textures 目录下 PNG 真实存在且命名一致。 |
|
||||||
|
| Essentia Cell Workbench「顶面材质没了,侧面正常」 | 直接打开 `models/block/essentia_cell_workbench.json` 看 `"textures"` 里 `"up"` 字段是不是写对了(应该指向 `textures/block/essentia_cell_workbench_top.png`) |
|
||||||
|
| 想加一个新物品/新方块 | ① 先在 ModItems / ModBlocks 里 `register("xxx", ...)`;② 想办法给物品弄一个 `models/item/xxx.json` + `textures/item/xxx.png`;如果是方块再加 `blockstates/xxx.json` + `models/block/xxx.json` + `textures/block/xxx.png`;③ 想进创造模式 Tab 要去 `ModCreativeTab.java` 里加 `output.accept(ModItems.XXX.get())`;④ 有配方去 `RecipeRegistration.java` 对应子方法里加一条 |
|
||||||
|
| 汉化文本 | `resources/assets/thaumicenergistics/lang/zh_cn.json` |
|
||||||
|
| 创造模式 Tab 物品顺序 | `java/thaumicenergistics/init/ModCreativeTab.java` |
|
||||||
|
| 源质单元容量实现(1k/4k/16k/64k 各自多少字节) | `java/thaumicenergistics/common/items/ItemEssentiaCell.java` 里的 `create1k / create4k / create16k / create64k` 静态工厂 |
|
||||||
|
| 天域之华 Tag 文件(防止主模组未来注册同名产生冲突) | `resources/data/thaumicenergistics/tags/items/wisp_essences.json` (里面写了 `"replace": false` 且包含 `{ "id": "#thaumcraft:wisp_essences", "required": false }` 作为兼容占位) |
|
||||||
|
| 启动崩 `Invalid arcane recipe ingredient: Ingredient@xxxx` | 一定是配方里把 Ingredient 传进了字符映射表(`'E', xxx` 的 xxx 必须是 ItemStack / Item / ResourceLocation,**不能是 Ingredient**),去 `registerShapedArcane()` 里搜 `wispEssenceIngredient`,有就立刻改成 `wisps.get(i)`(ItemStack) |
|
||||||
|
| Gradle 警告 `Space-assignment syntax ... init.gradle:10` | 这个是**全局 Gradle 初始化脚本**(`D:\GradleCache\init.gradle`)里的老写法,把 `url "https://..."` 改成 `url = "https://..."` 就好,跟模组代码无关 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、注册一个方块 — 从 Java 代码 → 材质 PNG 的完整 5 步引用链
|
||||||
|
|
||||||
|
> 🎯 例子用现成的 `knowledge_inscriber`(知识刻印机),你加新方块时直接把所有 `knowledge_inscriber` 替换成你的新注册 id 即可。
|
||||||
|
|
||||||
|
>
|
||||||
|
> 核心铁律:**注册 id 必须全局一致** — ModBlocks、ModItems、blockstates 文件名、models/block 文件名、models/item 文件名、textures 引用名,**全部都是同一个字符串**(大小写全小写,单词间下划线)。
|
||||||
|
|
||||||
|
### 第 1 步:Java 注册方块本体(决定注册 id)
|
||||||
|
**文件**:`java/thaumicenergistics/init/ModBlocks.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static final DeferredBlock<Block> KNOWLEDGE_INSCRIBER =
|
||||||
|
REGISTRY.register("knowledge_inscriber", () -> new Block(P));
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^ ← 这里是「注册 id」,下面 4 层必须全一致
|
||||||
|
|
||||||
|
```
|
||||||
|
如果方块有 GUI(比如 Essentia Cell Workbench),就在 `new Block(P) { ... }` 里覆写 `useItemOn` + `getMenuProvider`。
|
||||||
|
如果方块有 BlockEntity(比如 Essentia Provider),就用 `ThEBaseEntityBlock(P)` 并实现 `newBlockEntity`。
|
||||||
|
|
||||||
|
### 第 2 步:Java 注册方块物品(拿在手里的那个 Item)
|
||||||
|
**文件**:`java/thaumicenergistics/init/ModItems.java`
|
||||||
|
```java
|
||||||
|
public static final DeferredItem<blockitem> KNOWLEDGE_INSCRIBER_ITEM =
|
||||||
|
REGISTRY.registerSimpleBlockItem(ModBlocks.KNOWLEDGE_INSCRIBER);
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^ ← 直接绑定第 1 步的方块</blockitem>
|
||||||
|
|
||||||
|
```
|
||||||
|
`registerSimpleBlockItem` 是 NeoForge 封装好的,它会自动用**同一个注册 id**(这里就是 `knowledge_inscriber`)来生成 BlockItem,所以下面第 5 步「物品模型」的文件名也必须是 `knowledge_inscriber.json`。
|
||||||
|
|
||||||
|
### 第 3 步:BlockState(方块状态 → 方块模型的路由表)
|
||||||
|
**文件**:`resources/assets/thaumicenergistics/blockstates/knowledge_inscriber.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variants":
|
||||||
|
{
|
||||||
|
"":
|
||||||
|
{
|
||||||
|
"model": "thaumicenergistics:block/knowledge_inscriber"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
> 📌 解释:Minecraft 渲染一个方块时,先看方块状态属性(朝向、开关等),再查 blockstates 找到该状态对应哪个**方块模型 JSON**。
|
||||||
|
> 知识刻印机没有状态属性(不区分朝向),所以用 `""` 表示「默认变体」;如果是有朝向的方块(比如 Essentia Provider),那要写 `facing=north/south/east/west/up/down` 六个变体。
|
||||||
|
|
||||||
|
### 第 4 步:Block Model(方块模型 → 每个面用哪张 PNG)
|
||||||
|
**文件**:`resources/assets/thaumicenergistics/models/block/knowledge_inscriber.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"parent": "minecraft:block/cube_bottom_top",
|
||||||
|
"textures": {
|
||||||
|
"top": "thaumicenergistics:block/knowledge_inscriber_top",
|
||||||
|
"bottom": "thaumicenergistics:block/knowledge_inscriber_bottom",
|
||||||
|
"side": "thaumicenergistics:block/knowledge_inscriber_side"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
> 📌 解释:`particle` 是「破坏方块时飞溅的粒子贴图」,一般跟 `north` 用同一张。上面 6 个面(`up/down/north/south/east/west`)的字符串,**不要加 `.png` 后缀**,游戏会自动去 `textures/` 目录下找对应的 PNG。
|
||||||
|
>
|
||||||
|
> 📌 对应的 3 张贴图必须真实存在:
|
||||||
|
> - `resources/assets/thaumicenergistics/textures/block/knowledge_inscriber_top.png`(顶面)
|
||||||
|
> - `resources/assets/thaumicenergistics/textures/block/knowledge_inscriber_bottom.png`(底面)
|
||||||
|
> - `resources/assets/thaumicenergistics/textures/block/knowledge_inscriber_side.png`(四个侧面共用一张)
|
||||||
|
|
||||||
|
### 第 5 步:Item Model(物品模型 — 方块物品最简单写法:直接「继承」方块模型)
|
||||||
|
**文件**:`resources/assets/thaumicenergistics/models/item/knowledge_inscriber.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"parent": "thaumicenergistics:block/knowledge_inscriber"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
> 📌 解释:99% 的方块物品**不需要独立物品贴图**,直接 `parent` 指回第 4 步的方块模型,Minecraft 会自动把方块渲染器缩放成 3D 物品形态(就是你在背包、物品栏、地上掉落物看到的样子)。
|
||||||
|
>
|
||||||
|
> 只有当你想让「物品形态」跟「方块形态」长得不一样时(比如 AE2 的接口物品是 2D 图标),才需要单独写 layer0 引用物品 PNG(写法参考下一节纯物品)。
|
||||||
|
|
||||||
|
|
||||||
|
### ✅ 新方块加完后**还要做的 2 件事**(别漏)
|
||||||
|
1. **进创造 Tab**:`java/thaumicenergistics/init/ModCreativeTab.java` 的 `displayItems` 里补一行 `output.accept(ModItems.KNOWLEDGE_INSCRIBER_ITEM.get());`(不然拿不到)
|
||||||
|
2. **汉化**:`resources/assets/thaumicenergistics/lang/zh_cn.json` 里补 `","block.thaumicenergistics.knowledge_inscriber":"知识刻印机"`
|
||||||
|
3. (可选)**有配方**:去 `RecipeRegistration.java` 的 `registerShapedArcane()` 或 `registerInfusion()` 里加一条。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、注册一个纯物品(不是方块) — 从 Java 代码 → 材质 PNG 的完整 3 步引用链
|
||||||
|
|
||||||
|
> 🎯 例子用现成的 `storage_component_1k`(1k 存储组件),加新物品时直接替换 id 即可。
|
||||||
|
> 核心铁律同上:注册 id = 模型文件名 = PNG 文件名(不加后缀)。
|
||||||
|
|
||||||
|
### 第 1 步:Java 注册物品本体
|
||||||
|
**文件**:`java/thaumicenergistics/init/ModItems.java`
|
||||||
|
```java
|
||||||
|
public static final DeferredItem<item> STORAGE_COMPONENT_1K = REGISTRY.registerSimpleItem("storage_component_1k");
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^ ← 注册 id,下面 2 层必须一致</item>
|
||||||
|
|
||||||
|
```
|
||||||
|
如果物品有自定义逻辑(比如源质 Cell 带容量读写 NBT、右键使用、背包叠加数量 1 等),就不用 `registerSimpleItem`,改用:
|
||||||
|
```java
|
||||||
|
public static final DeferredItem<itemessentiacell> ESSENTIA_CELL_1K =
|
||||||
|
REGISTRY.registerItem("essentia_cell_1k", ItemEssentiaCell::create1k);
|
||||||
|
|
||||||
|
```
|
||||||
|
本质上只是把 `new Item()` 换成你自己的自定义物品构造函数 / 静态工厂。
|
||||||
|
|
||||||
|
### 第 2 步:Item Model(物品模型 → layer0 指向物品 PNG)
|
||||||
|
**文件**:`resources/assets/thaumicenergistics/models/item/storage_component_1k.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"parent": "minecraft:item/generated",
|
||||||
|
"textures": {
|
||||||
|
"layer0": "thaumicenergistics:item/storage_component_1k"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
> 📌 解释:所有「普通 2D 贴图物品」的 parent 都是 `item/generated`(MC 内置的平面图标渲染器)。
|
||||||
|
> `layer0` 就是「主贴图」。字符串**不要加 `.png` 后缀**,游戏会自动找 `textures/item/storage_component_1k.png`。
|
||||||
|
>
|
||||||
|
> 少数物品有动画(比如 `knowledge_core.png.mcmeta`),那是在 PNG 旁边放一个 `.mcmeta` JSON 描述帧动画,模型文件写法不变。
|
||||||
|
|
||||||
|
### 第 3 步:放真实贴图 PNG
|
||||||
|
**文件**:`resources/assets/thaumicenergistics/textures/item/storage_component_1k.png`
|
||||||
|
> 📌 要求:16×16 或 32×32 或 64×64 的 PNG(尺寸必须是 2 的幂),**文件名必须跟 layer0 写的一样**。
|
||||||
|
|
||||||
Reference in New Issue
Block a user