This commit is contained in:
1820982382
2026-08-03 19:25:28 +08:00
parent 7b341980bd
commit f9ddc599e1
69 changed files with 832 additions and 432 deletions
@@ -7,7 +7,6 @@ import appeng.api.stacks.AEKeyTypesInternal;
import appeng.api.upgrades.Upgrades;
import appeng.client.gui.style.StyleManager;
import appeng.core.definitions.AEItems;
import appeng.core.localization.GuiText;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
@@ -118,27 +117,30 @@ public final class ThaumicEnergistics {
private void registerUpgrades(FMLCommonSetupEvent event) {
event.enqueueWork(() -> {
String essentiaIoBusGroup = GuiText.IOBuses.getTranslationKey();
// 注:不传 group——带 group 会在卡 tooltip 中与 AE2 原版 IO 总线合并显示为
// "ME Import/Export Bus"(同 group 跳过),ThE 设备名不显示。与存储总线一致,各自显示设备名。
var importBusItem = ModItems.ESSENTIA_IMPORT_BUS.get();
Upgrades.add(AEItems.FUZZY_CARD, importBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.REDSTONE_CARD, importBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.CAPACITY_CARD, importBusItem, 5, essentiaIoBusGroup);
Upgrades.add(AEItems.SPEED_CARD, importBusItem, 4, essentiaIoBusGroup);
Upgrades.add(AEItems.INVERTER_CARD, importBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.FUZZY_CARD, importBusItem, 1);
Upgrades.add(AEItems.REDSTONE_CARD, importBusItem, 1);
Upgrades.add(AEItems.CAPACITY_CARD, importBusItem, 5);
Upgrades.add(AEItems.SPEED_CARD, importBusItem, 4);
Upgrades.add(AEItems.INVERTER_CARD, importBusItem, 1);
var exportBusItem = ModItems.ESSENTIA_EXPORT_BUS.get();
Upgrades.add(AEItems.FUZZY_CARD, exportBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.REDSTONE_CARD, exportBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.CAPACITY_CARD, exportBusItem, 5, essentiaIoBusGroup);
Upgrades.add(AEItems.SPEED_CARD, exportBusItem, 4, essentiaIoBusGroup);
Upgrades.add(AEItems.CRAFTING_CARD, exportBusItem, 1, essentiaIoBusGroup);
Upgrades.add(AEItems.FUZZY_CARD, exportBusItem, 1);
Upgrades.add(AEItems.REDSTONE_CARD, exportBusItem, 1);
Upgrades.add(AEItems.CAPACITY_CARD, exportBusItem, 5);
Upgrades.add(AEItems.SPEED_CARD, exportBusItem, 4);
Upgrades.add(AEItems.CRAFTING_CARD, exportBusItem, 1);
var storageBusItem = ModItems.ESSENTIA_STORAGE_BUS.get();
Upgrades.add(AEItems.FUZZY_CARD, storageBusItem, 1);
Upgrades.add(AEItems.INVERTER_CARD, storageBusItem, 1);
Upgrades.add(AEItems.CAPACITY_CARD, storageBusItem, 5);
Upgrades.add(AEItems.VOID_CARD, storageBusItem, 1);
// 神秘装配台:加速卡(AEItems.SPEED_CARD = Acceleration Card,tooltip 名单 + 装配台升级槽可装——加快合成)
Upgrades.add(AEItems.SPEED_CARD, ModItems.ARCANE_ASSEMBLER_ITEM.get(), 4);
});
}
@@ -7,6 +7,8 @@ import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import thaumcraft.api.aspects.Aspect;
import thaumcraft.client.gui.AspectGuiRenderer;
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
import java.util.ArrayList;
@@ -16,29 +18,13 @@ public class EssentiaKeyRenderHandler implements appeng.api.client.AEKeyRenderHa
@Override
public void drawInGui(Minecraft minecraft, GuiGraphics guiGraphics, int x, int y, AEssentiaKey stack) {
// 复用 TC 的 AspectGuiRenderer(positionTexColor shader + enableBlend)——透明背景正确,
// 与蒸馏编码台一致;避免 GuiGraphics.blit 的 blend 状态问题(图标内部镂空被填黑)。
String tag = stack.getId().getPath();
// 渲染要素图标(透明背景):TC 的 aspect 图标是白色剪影,需用 aspect 颜色着色(与 JEI 的 AspectGuiRenderer 一致)
int color = getAspectColor(tag);
float r = ((color >> 16) & 0xFF) / 255.0f;
float g = ((color >> 8) & 0xFF) / 255.0f;
float b = (color & 0xFF) / 255.0f;
guiGraphics.setColor(r, g, b, 1.0f);
guiGraphics.blit(aspectTexture(tag), x, y, 0, 0, 16, 16, 16, 16);
guiGraphics.setColor(1.0f, 1.0f, 1.0f, 1.0f);
}
/** 按 tag 缓存 aspect 贴图 RL,避免每帧 fromNamespaceAndPath。 */
private static final java.util.Map<String, ResourceLocation> TEXTURE_CACHE = new java.util.HashMap<>();
private static ResourceLocation aspectTexture(String tag) {
return TEXTURE_CACHE.computeIfAbsent(tag,
t -> ResourceLocation.fromNamespaceAndPath("thaumcraft", "textures/aspects/" + t + ".png"));
}
/** 获取 aspect 的 ARGB 颜色(按 tag 查 Thaumcraft Aspect),未知返回白色。 */
private static int getAspectColor(String tag) {
thaumcraft.api.aspects.Aspect aspect = thaumcraft.api.aspects.Aspect.getAspect(tag);
return aspect != null ? aspect.getColor() : 0xFFFFFFFF;
Aspect aspect = Aspect.getAspect(tag);
if (aspect != null) {
AspectGuiRenderer.draw(guiGraphics, aspect, x, y, 16, 1.0f);
}
}
@Override
@@ -32,7 +32,7 @@ public class ContainerArcaneAssembler extends AbstractContainerMenu {
// ===== 坐标常量(1:1 对应1.7.10) =====
private static final int PLAYER_INV_X = 8;
private static final int PLAYER_INV_Y = 115;
private static final int PLAYER_INV_Y = 113;
private static final int HOTBAR_Y = PLAYER_INV_Y + 58; // 173
private static final int PATTERN_X = 26;
@@ -29,8 +29,8 @@ import java.util.List;
public class ContainerDistillationPatternEncoder extends AbstractContainerMenu {
private static final int PLAYER_INV_Y = 152;
private static final int HOTBAR_Y = 210;
private static final int PLAYER_INV_Y = 150;
private static final int HOTBAR_Y = 208;
private static final int PLAYER_INV_X = 8;
private static final int SLOT_SOURCE_X = 15;
@@ -48,8 +48,8 @@ public class ContainerEssentiaCellWorkbench extends AbstractContainerMenu {
IItemHandler cellHandler = workbench != null ? workbench.getCellInventory() : fallbackCell;
addSlot(new SlotItemHandler(cellHandler, 0, 152, 8));
for (int r = 0; r < 3; r++) for (int c = 0; c < 9; c++) addSlot(new Slot(inv, c + r * 9 + 9, 8 + c * 18, 169 + r * 18));
for (int c = 0; c < 9; c++) addSlot(new Slot(inv, c, 8 + c * 18, 227));
for (int r = 0; r < 3; r++) for (int c = 0; c < 9; c++) addSlot(new Slot(inv, c + r * 9 + 9, 8 + c * 18, 167 + r * 18));
for (int c = 0; c < 9; c++) addSlot(new Slot(inv, c, 8 + c * 18, 225));
}
/** 客户端打开菜单用(IMenuTypeExtension 注册);workbench 为 null,分区操作走 C2S 到服务端。 */
@@ -45,8 +45,8 @@ public class ContainerKnowledgeInscriber extends AbstractContainerMenu {
private static final int MAXIMUM_PATTERNS = HandlerKnowledgeCore.MAXIMUM_STORED_PATTERNS;
private static final int PLAYER_INV_Y = 162;
private static final int HOTBAR_Y = 220;
private static final int PLAYER_INV_Y = 160;
private static final int HOTBAR_Y = 218;
private static final int PLAYER_INV_X = 8;
private static final int KCORE_SLOT_X = 186;
@@ -25,7 +25,7 @@ public class GuiEssentiaCellWorkbench extends AbstractContainerScreen<ContainerE
private static final ResourceLocation TEX = ResourceLocation.fromNamespaceAndPath(ThaumicEnergistics.MODID, "textures/gui/essentia_cell_workbench.png");
private static final int GUI_WIDTH = 176, GUI_HEIGHT = 251;
/** 分区格布局常量(JEI ghost 拖拽也使用,故公开)。 */
public static final int WIDGET_POS_X = 7, WIDGET_POS_Y = 28;
public static final int WIDGET_POS_X = 8, WIDGET_POS_Y = 29;
public static final int WIDGETS_PER_ROW = 9, WIDGET_ROWS = 7, NUMBER_OF_WIDGETS = 63;
public static final int WIDGET_SIZE = 18;
@@ -50,6 +50,8 @@ public final class RecipeRegistration {
public static ShapelessArcaneRecipe ARCANE_CRAFTING_TERMINAL;
public static ShapelessArcaneRecipe VIS_INTERFACE;
public static ShapelessArcaneRecipe ESSENTIA_VIBRATION_CHAMBER;
/** 赛特斯石英复制(坩埚炼金配方——研究页关联)。 */
public static CrucibleRecipe CERTUS_DUPE_RECIPE;
private static final TagKey<Item> WISP_ESSENCES = tag("thaumicenergistics", "wisp_essences");
@@ -62,9 +64,21 @@ public final class RecipeRegistration {
registerShapedArcane();
registerShapelessArcane();
registerInfusion();
registerCrucible();
ThaumicEnergistics.LOG.info("ThaumicEnergistics recipes registered.");
}
/** 坩埚炼金配方:赛特斯石英复制(丢 1 赛特斯石英 + 水晶/交换要素 → 2 赛特斯石英)。 */
private static void registerCrucible() {
ItemStack quartz = ae("certus_quartz_crystal");
if (quartz.isEmpty()) return;
CERTUS_DUPE_RECIPE = ThaumcraftApi.addCrucibleRecipe(
"CERTUS_DUPE",
quartz.copyWithCount(2),
quartz.copyWithCount(1),
new AspectList().add(Aspect.CRYSTAL, 5).add(Aspect.EXCHANGE, 5));
}
private static void registerShapedArcane() {
STORAGE_COMPONENT_1K = ThaumcraftApi.addArcaneCraftingRecipe("ESSENTIASTORAGE1K", stack(ModItems.STORAGE_COMPONENT_1K),
new AspectList().add(Aspect.FIRE, 3).add(Aspect.ORDER, 1),
@@ -4,13 +4,14 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
import thaumcraft.api.aspects.Aspect;
import thaumcraft.api.aspects.AspectList;
import thaumcraft.api.aspects.IAspectContainer;
import thaumicenergistics.ThaumicEnergistics;
import thaumcraft.common.essentia.EssentiaTransport;
import thaumicenergistics.api.storage.AspectStack;
public final class TCReflection {
@@ -18,16 +19,69 @@ public final class TCReflection {
/** aspect tag → ResourceLocation 缓存(tag 有界:6 个源质 + 已注册方面)。 */
private static final Map<String, ResourceLocation> ASPECT_ID_CACHE = new ConcurrentHashMap<>();
/** 荒古炼金炉类名(TC port;不实现 IAspectContainer,需反射抽内部源质)。 */
private static final String FURNACE_CLASS_NAME = "thaumcraft.common.blockentities.AlchemicalFurnaceBlockEntity";
/** 炼金炉 private aspects 字段(缓存 Field 避免每 tick getDeclaredField)。 */
private static volatile java.lang.reflect.Field furnaceAspectsField;
private TCReflection() {}
/** 从荒古炼金炉内部源质缓存直接抽取(替代蒸馏釜/罐子中转)。 */
@Nullable
private static AspectStack extractFromFurnace(BlockEntity be, int maxAmount) {
try {
if (furnaceAspectsField == null) {
furnaceAspectsField = be.getClass().getDeclaredField("aspects");
furnaceAspectsField.setAccessible(true);
}
AspectList furnaceAspects = (AspectList) furnaceAspectsField.get(be);
if (furnaceAspects != null && furnaceAspects.size() > 0) {
Aspect[] aspects = furnaceAspects.getAspects();
for (Aspect aspect : aspects) {
if (aspect == null) continue;
int amt = furnaceAspects.getAmount(aspect);
if (amt <= 0) continue;
int toExtract = Math.min(amt, maxAmount);
furnaceAspects.remove(aspect, toExtract);
be.setChanged();
ResourceLocation id = getAspectId(aspect);
return id != null ? new AspectStack(id, toExtract) : null;
}
}
} catch (Exception ignored) {
}
return null;
}
@Nullable
public static AspectStack extractEssentia(@Nullable BlockEntity be, int maxAmount) {
if (be == null) {
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: be is null");
return null;
}
// ===== 路径0: 荒古炼金炉直连(AlchemicalFurnaceBlockEntity 不实现 IAspectContainer,
// 源质在 private aspects 缓存——直接抽取,替代"必须经蒸馏釜/罐子")=====
if (FURNACE_CLASS_NAME.equals(be.getClass().getName())) {
AspectStack fromFurnace = extractFromFurnace(be, maxAmount);
if (fromFurnace != null) return fromFurnace;
}
// ===== 路径0b: TC 管道接口(EssentiaTransport——高级荒古炼金炉喷嘴/源质管道)=====
if (be instanceof EssentiaTransport transport) {
for (Direction face : Direction.values()) {
if (!transport.canOutputTo(face)) continue;
Aspect aspect = transport.getEssentiaType(face);
if (aspect == null) continue;
int amount = transport.getEssentiaAmount(face);
if (amount <= 0) continue;
int toExtract = Math.min(amount, maxAmount);
int taken = transport.takeEssentia(aspect, toExtract, face);
if (taken > 0) {
be.setChanged();
ResourceLocation id = getAspectId(aspect);
return id != null ? new AspectStack(id, taken) : null;
}
}
}
if (!(be instanceof IAspectContainer container)) {
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: {} does not implement IAspectContainer", be.getClass().getName());
return null;
}
@@ -36,7 +90,6 @@ public final class TCReflection {
if (aspectList != null && aspectList.size() > 0) {
Aspect[] aspects = aspectList.getAspects();
if (aspects != null) {
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: found {} aspects via getAspects()", aspects.length);
for (Aspect aspect : aspects) {
if (aspect == null) continue;
int amt = aspectList.getAmount(aspect);
@@ -45,7 +98,6 @@ public final class TCReflection {
if (container.takeFromContainer(aspect, toExtract)) {
be.setChanged();
ResourceLocation id = getAspectId(aspect);
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: SUCCESS (API) - extracted {} of {}", toExtract, id);
return id != null ? new AspectStack(id, toExtract) : null;
}
}
@@ -53,7 +105,6 @@ public final class TCReflection {
}
// ===== 路径B: TC 移植版反射回退 (getAspect/getAmount) =====
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: getAspects() empty, trying reflection fallback for {}", be.getClass().getName());
try {
java.lang.reflect.Method[] methods = getAspectMethods(be.getClass());
if (methods.length < 2) {
@@ -66,18 +117,12 @@ public final class TCReflection {
if (container.takeFromContainer(aspect, toExtract)) {
be.setChanged();
ResourceLocation id = getAspectId(aspect);
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: SUCCESS (reflection) - extracted {} of {}", toExtract, id);
return id != null ? new AspectStack(id, toExtract) : null;
}
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: takeFromContainer returned false for {} of {}", toExtract, aspect.getTag());
} else {
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: reflection got aspect={}, amount={}", aspect, amount);
}
} catch (Exception e) {
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: reflection fallback error: {}", e.getMessage());
} catch (Exception ignored) {
}
ThaumicEnergistics.LOG.debug("[TCReflection] extractEssentia: no extractable aspect found");
return null;
}
@@ -96,10 +141,8 @@ public final class TCReflection {
Aspect aspect = findAspectById(stack.aspectId());
if (aspect == null) return 0;
int toInsert = (int) stack.amount();
ThaumicEnergistics.LOG.debug("[TCReflection] insertEssentia: inserting {} of {} into {}", toInsert, stack.aspectId(), be.getClass().getName());
int leftover = container.addToContainer(aspect, toInsert);
int added = toInsert - leftover;
ThaumicEnergistics.LOG.debug("[TCReflection] insertEssentia: added={}, leftover={}", added, leftover);
if (added > 0) {
be.setChanged();
}
@@ -58,7 +58,7 @@ public final class ThEThaumcraft {
ResearchCategories.registerCategory(TAB,
ResourceLocation.fromNamespaceAndPath(MODID, "textures/research/tab_icon.png"),
ResourceLocation.fromNamespaceAndPath("thaumcraft", "textures/gui/gui_researchback.png"));
ResourceLocation.fromNamespaceAndPath(MODID, "textures/research/research_background.png"));
registerScanEvent();
@@ -346,7 +346,9 @@ public final class ThEThaumcraft {
new AspectList().add(Aspect.CRYSTAL, 5).add(Aspect.MAGIC, 5).add(Aspect.EXCHANGE, 5),
-5, -5, 2, new ItemStack(appeng.core.definitions.AEItems.CERTUS_QUARTZ_CRYSTAL.asItem()))
.setRound().setParents("PSEUDO_ALCHEMICALDUPLICATION").setParentsHidden("BASEENERGISTICS")
.setPages(new ResearchPage("tc.research_text.CERTUS_DUPE"))
.setPages(
new ResearchPage("tc.research_text.CERTUS_DUPE"),
new ResearchPage(RecipeRegistration.CERTUS_DUPE_RECIPE))
.registerResearchItem();
}
@@ -83,9 +83,10 @@ public class EssentiaExportBusPart extends IOBusPart
@Override
public void getBoxes(IPartCollisionHelper bch) {
bch.addBox(6, 6, 11, 10, 10, 13);
bch.addBox(5, 5, 13, 11, 11, 14);
bch.addBox(4, 4, 14, 12, 12, 16);
bch.addBox(4, 4, 12, 12, 12, 14);
bch.addBox(5, 5, 14, 11, 11, 15);
bch.addBox(6, 6, 15, 10, 10, 16);
bch.addBox(6, 6, 11, 10, 10, 12);
}
@Override
@@ -20,10 +20,12 @@ public final class ModBlocks {
private ModBlocks() {}
public static final DeferredRegister.Blocks REGISTRY = DeferredRegister.createBlocks(ThaumicEnergistics.MODID);
private static final BlockBehaviour.Properties P = BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(2f).sound(SoundType.STONE);
/** 装配器专用:noOcclusion——玻璃方块不被视为不透明,相邻方块(如冰/半透明)接触面不被剔除,透明玻璃后可正常看到后方方块。 */
private static final BlockBehaviour.Properties PA = P.noOcclusion();
public static final DeferredBlock<Block> ESSENTIA_PROVIDER = REGISTRY.register("essentia_provider", () -> new ThEBaseEntityBlock(P) { public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState st) { return new TileEssentiaProvider(pos, st); } });
public static final DeferredBlock<Block> INFUSION_PROVIDER = REGISTRY.register("infusion_provider", () -> new ThEBaseEntityBlock(P) { public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState st) { return new TileInfusionProvider(pos, st); } });
public static final DeferredBlock<Block> ARCANE_ASSEMBLER = REGISTRY.register("arcane_assembler", () -> new ThEBaseEntityBlock(P) {
public static final DeferredBlock<Block> ARCANE_ASSEMBLER = REGISTRY.register("arcane_assembler", () -> new ThEBaseEntityBlock(PA) {
public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState st) { return new TileArcaneAssembler(pos, st); }
@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) {
@@ -53,6 +55,14 @@ public final class ModBlocks {
public static final DeferredBlock<Block> KNOWLEDGE_INSCRIBER = REGISTRY.register("knowledge_inscriber", () -> new ThEBaseEntityBlock(P) {
public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState st) { return new TileKnowledgeInscriber(pos, st); }
@Override
protected void createBlockStateDefinition(net.minecraft.world.level.block.state.StateDefinition.Builder<Block, BlockState> builder) {
builder.add(OrientableEntityBlock.FACING);
}
@Override
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
return this.defaultBlockState().setValue(OrientableEntityBlock.FACING, context.getHorizontalDirection().getOpposite());
}
@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.isClientSide) player.openMenu(state.getMenuProvider(level, pos));
return net.minecraft.world.ItemInteractionResult.SUCCESS;
@@ -68,6 +78,14 @@ public final class ModBlocks {
public static final DeferredBlock<Block> DISTILLATION_ENCODER = REGISTRY.register("distillation_encoder", () -> new ThEBaseEntityBlock(P) {
public net.minecraft.world.level.block.entity.BlockEntity newBlockEntity(BlockPos pos, BlockState st) { return new TileDistillationPatternEncoder(pos, st); }
@Override
protected void createBlockStateDefinition(net.minecraft.world.level.block.state.StateDefinition.Builder<Block, BlockState> builder) {
builder.add(OrientableEntityBlock.FACING);
}
@Override
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
return this.defaultBlockState().setValue(OrientableEntityBlock.FACING, context.getHorizontalDirection().getOpposite());
}
@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.isClientSide) player.openMenu(state.getMenuProvider(level, pos));
return net.minecraft.world.ItemInteractionResult.SUCCESS;