建立gitignore
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package tckubejsbridge;
|
||||
|
||||
import net.neoforged.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* TC KubeJS Bridge — 将神秘时代4(Thaumcraft 4 移植版)的配方系统桥接到 KubeJS。
|
||||
*
|
||||
* 提供 KubeJS 脚本可调用的:
|
||||
* - event.recipes.thaumcraft.infusion(...) 注魔配方
|
||||
* - event.recipes.thaumcraft.crucible(...) 坩埚炼金配方
|
||||
* - event.recipes.thaumcraft.arcane(...) 奥术有序合成配方
|
||||
* - event.recipes.thaumcraft.arcane_shapeless(...) 奥术无序合成配方
|
||||
* - event.thaumcraft.addObjectTag(...) 物品固有要素
|
||||
*
|
||||
* 实际注册逻辑在 {@link tckubejsbridge.kubejs.TCKubeJSPlugin}(KubeJS 插件)。
|
||||
*/
|
||||
@Mod(TCKubeJSBridge.MODID)
|
||||
public final class TCKubeJSBridge {
|
||||
public static final String MODID = "tckubejsbridge";
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
|
||||
import dev.latvian.mods.kubejs.recipe.RecipeKey;
|
||||
import dev.latvian.mods.kubejs.recipe.component.ComponentRole;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponentType;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponentValue;
|
||||
import dev.latvian.mods.kubejs.recipe.component.SimpleRecipeComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponentWithCodec;
|
||||
import dev.latvian.mods.kubejs.script.ConsoleJS;
|
||||
import dev.latvian.mods.rhino.type.TypeInfo;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 将脚本中的源质列表转换为 Thaumcraft {@link AspectList}。
|
||||
*
|
||||
* 支持两种脚本写法:
|
||||
* <pre>{@code
|
||||
* ['ignis 64', 'potentia 32'] // 字符串数组:"要素名 数量"
|
||||
* { ignis: 64, potentia: 32 } // 对象:{ 要素名: 数量 }
|
||||
* }</pre>
|
||||
*/
|
||||
public final class AspectListComponent implements RecipeComponent<AspectList> {
|
||||
|
||||
public static final AspectListComponent INSTANCE = new AspectListComponent();
|
||||
|
||||
private static final RecipeComponentType<?> TYPE = RecipeComponentType.unit(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "aspect_list"),
|
||||
AspectListComponent.INSTANCE);
|
||||
|
||||
/** 要素名 → 数量 的对象/数组 编解码(仅作序列化展示,桥接时直接读值) */
|
||||
private static final Codec<AspectList> CODEC = Codec.unboundedMap(Codec.STRING, Codec.INT)
|
||||
.flatXmap(
|
||||
map -> {
|
||||
try {
|
||||
return DataResult.success(fromMap(map));
|
||||
} catch (Exception e) {
|
||||
return DataResult.error(() -> "Invalid aspect list: " + e.getMessage());
|
||||
}
|
||||
},
|
||||
list -> {
|
||||
try {
|
||||
return DataResult.success(toMap(list));
|
||||
} catch (Exception e) {
|
||||
return DataResult.error(() -> "Invalid aspect list: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
private AspectListComponent() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeComponentType<?> type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec<AspectList> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInfo typeInfo() {
|
||||
return TypeInfo.of(Object.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AspectList wrap(dev.latvian.mods.kubejs.recipe.RecipeScriptContext cx, Object from) {
|
||||
if (from instanceof AspectList list) {
|
||||
return list;
|
||||
}
|
||||
if (from instanceof String s) {
|
||||
return parseString(s);
|
||||
}
|
||||
if (from instanceof java.util.List<?> list) {
|
||||
AspectList result = new AspectList();
|
||||
for (Object o : list) {
|
||||
if (o instanceof String s) {
|
||||
result.add(parseString(s));
|
||||
} else if (o instanceof AspectList al) {
|
||||
result.add(al);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (from instanceof Map<?, ?> map) {
|
||||
return fromMap(map);
|
||||
}
|
||||
// 兜底:尝试从 JSON 元素解析
|
||||
if (from instanceof JsonElement json) {
|
||||
if (json.isJsonObject()) {
|
||||
return fromJsonObject(json.getAsJsonObject());
|
||||
}
|
||||
if (json.isJsonArray()) {
|
||||
AspectList result = new AspectList();
|
||||
for (JsonElement e : json.getAsJsonArray()) {
|
||||
if (e.isJsonPrimitive() && e.getAsJsonPrimitive().isString()) {
|
||||
result.add(parseString(e.getAsString()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot convert " + from + " to AspectList");
|
||||
}
|
||||
|
||||
private static AspectList parseString(String s) {
|
||||
String[] parts = s.trim().split("\\s+");
|
||||
Aspect aspect = Aspect.getAspect(parts[0]);
|
||||
if (aspect == null) {
|
||||
throw new IllegalArgumentException("Unknown aspect '" + parts[0] + "'");
|
||||
}
|
||||
int amount = parts.length > 1 ? Integer.parseInt(parts[1]) : 1;
|
||||
return new AspectList().add(aspect, amount);
|
||||
}
|
||||
|
||||
private static AspectList fromMap(Map<?, ?> map) {
|
||||
AspectList result = new AspectList();
|
||||
for (Map.Entry<?, ?> e : map.entrySet()) {
|
||||
Aspect aspect = Aspect.getAspect(String.valueOf(e.getKey()));
|
||||
if (aspect == null) {
|
||||
throw new IllegalArgumentException("Unknown aspect '" + e.getKey() + "'");
|
||||
}
|
||||
result.add(aspect, ((Number) e.getValue()).intValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static AspectList fromJsonObject(JsonObject obj) {
|
||||
AspectList result = new AspectList();
|
||||
for (Map.Entry<String, JsonElement> e : obj.entrySet()) {
|
||||
Aspect aspect = Aspect.getAspect(e.getKey());
|
||||
if (aspect == null) {
|
||||
throw new IllegalArgumentException("Unknown aspect '" + e.getKey() + "'");
|
||||
}
|
||||
result.add(aspect, e.getValue().getAsInt());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> toMap(AspectList list) {
|
||||
java.util.LinkedHashMap<String, Integer> map = new java.util.LinkedHashMap<>();
|
||||
for (Aspect aspect : list.getAspects()) {
|
||||
if (aspect != null) {
|
||||
map.put(aspect.getTag(), list.getAmount(aspect));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToJson(KubeRecipe recipe, RecipeComponentValue<AspectList> cv, JsonObject json) {
|
||||
JsonObject obj = new JsonObject();
|
||||
for (Aspect aspect : cv.value.getAspects()) {
|
||||
if (aspect != null) {
|
||||
obj.add(aspect.getTag(), new JsonPrimitive(cv.value.getAmount(aspect)));
|
||||
}
|
||||
}
|
||||
json.add(cv.key.name, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromJson(KubeRecipe recipe, RecipeComponentValue<AspectList> cv, JsonObject json) {
|
||||
JsonElement v = json.get(cv.key.name);
|
||||
if (v != null && v.isJsonObject()) {
|
||||
cv.value = fromJsonObject(v.getAsJsonObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
/**
|
||||
* 坩埚炼金配方 KubeRecipe:event.recipes.thaumcraft.crucible(research, result, catalyst, aspects)
|
||||
*/
|
||||
public class CrucibleRecipeKJS extends TCRecipeKJS {
|
||||
|
||||
public CrucibleRecipeKJS() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bridgeToTC() {
|
||||
String research = getValue(TCKubeJSPlugin.RESEARCH);
|
||||
ItemStack result = getValue(TCKubeJSPlugin.RESULT);
|
||||
Object catalyst = getValue(TCKubeJSPlugin.CATALYST);
|
||||
AspectList aspects = getValue(TCKubeJSPlugin.ASPECTS);
|
||||
|
||||
TCRecipeBridge.registerCrucible(research, result, catalyst, aspects);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 注魔配方 KubeRecipe:event.recipes.thaumcraft.infusion(research, result, instability, aspects, central, ...components)
|
||||
*/
|
||||
public class InfusionRecipeKJS extends TCRecipeKJS {
|
||||
|
||||
public InfusionRecipeKJS() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bridgeToTC() {
|
||||
String research = getValue(TCKubeJSPlugin.RESEARCH);
|
||||
ItemStack result = getValue(TCKubeJSPlugin.RESULT);
|
||||
int instability = getValue(TCKubeJSPlugin.INSTABILITY);
|
||||
AspectList aspects = getValue(TCKubeJSPlugin.ASPECTS);
|
||||
ItemStack central = getValue(TCKubeJSPlugin.CENTRAL);
|
||||
List<ItemStack> components = getValue(TCKubeJSPlugin.COMPONENTS);
|
||||
|
||||
TCRecipeBridge.registerInfusion(research, result, instability, aspects, central,
|
||||
components == null ? new ItemStack[0] : components.toArray(new ItemStack[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 奥术有序合成配方 KubeRecipe:
|
||||
* event.recipes.thaumcraft.arcane(research, result, aspects, pattern, key)
|
||||
*
|
||||
* 其中 pattern 为行字符串数组(如 ['ABA','BCB','ABA']),
|
||||
* key 为字符→物品映射(如 {A:'minecraft:iron_ingot', B:Item.of('minecraft:gold_ingot')})。
|
||||
*/
|
||||
public class ShapedArcaneRecipeKJS extends TCRecipeKJS {
|
||||
|
||||
public ShapedArcaneRecipeKJS() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bridgeToTC() {
|
||||
String research = getValue(TCKubeJSPlugin.RESEARCH);
|
||||
ItemStack result = getValue(TCKubeJSPlugin.RESULT);
|
||||
AspectList aspects = getValue(TCKubeJSPlugin.ASPECTS);
|
||||
List<String> pattern = getValue(TCKubeJSPlugin.PATTERN);
|
||||
dev.latvian.mods.kubejs.util.TinyMap<Character, ItemStack> key = getValue(TCKubeJSPlugin.KEY);
|
||||
|
||||
// 组装 TC addArcaneCraftingRecipe 参数:pattern 行 + 字符/物品映射
|
||||
List<Object> args = new ArrayList<>();
|
||||
if (pattern != null) {
|
||||
args.addAll(pattern);
|
||||
}
|
||||
if (key != null) {
|
||||
for (dev.latvian.mods.kubejs.util.TinyMap.Entry<Character, ItemStack> e : key.entries()) {
|
||||
args.add(e.key());
|
||||
args.add(e.value());
|
||||
}
|
||||
}
|
||||
|
||||
TCRecipeBridge.registerArcane(research, result, aspects, args.toArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 奥术无序合成配方 KubeRecipe:
|
||||
* event.recipes.thaumcraft.arcane_shapeless(research, result, aspects, ...ingredients)
|
||||
*/
|
||||
public class ShapelessArcaneRecipeKJS extends TCRecipeKJS {
|
||||
|
||||
public ShapelessArcaneRecipeKJS() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bridgeToTC() {
|
||||
String research = getValue(TCKubeJSPlugin.RESEARCH);
|
||||
ItemStack result = getValue(TCKubeJSPlugin.RESULT);
|
||||
AspectList aspects = getValue(TCKubeJSPlugin.ASPECTS);
|
||||
List<ItemStack> ingredients = getValue(TCKubeJSPlugin.INGREDIENTS);
|
||||
|
||||
TCRecipeBridge.registerArcaneShapeless(research, result, aspects,
|
||||
ingredients == null ? new ItemStack[0] : ingredients.toArray(new ItemStack[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import dev.latvian.mods.kubejs.plugin.KubeJSPlugin;
|
||||
import dev.latvian.mods.kubejs.recipe.RecipeKey;
|
||||
import dev.latvian.mods.kubejs.recipe.component.ComponentRole;
|
||||
import dev.latvian.mods.kubejs.recipe.component.ItemStackComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.component.NumberComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponentType;
|
||||
import dev.latvian.mods.kubejs.recipe.component.RecipeComponentTypeRegistry;
|
||||
import dev.latvian.mods.kubejs.recipe.component.StringComponent;
|
||||
import dev.latvian.mods.kubejs.recipe.schema.KubeRecipeFactory;
|
||||
import dev.latvian.mods.kubejs.recipe.schema.RecipeSchema;
|
||||
import dev.latvian.mods.kubejs.recipe.schema.RecipeSchemaRegistry;
|
||||
import dev.latvian.mods.kubejs.script.BindingRegistry;
|
||||
import dev.latvian.mods.kubejs.script.ConsoleJS;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* KubeJS 插件:将 Thaumcraft 配方系统桥接到 KubeJS 脚本。
|
||||
*
|
||||
* 提供:
|
||||
* <ul>
|
||||
* <li>event.recipes.thaumcraft.infusion(...) — 注魔配方</li>
|
||||
* <li>event.recipes.thaumcraft.crucible(...) — 坩埚炼金配方</li>
|
||||
* <li>event.recipes.thaumcraft.arcane(...) — 奥术有序合成配方</li>
|
||||
* <li>event.recipes.thaumcraft.arcane_shapeless(...) — 奥术无序合成配方</li>
|
||||
* <li>event.thaumcraft.addObjectTag(...) — 物品固有要素</li>
|
||||
* </ul>
|
||||
*
|
||||
* 发现方式:{@code kubejs.plugins.txt} 列出本类全名。
|
||||
*/
|
||||
public class TCKubeJSPlugin implements KubeJSPlugin {
|
||||
|
||||
// ---- 共享 RecipeKey ----
|
||||
static final RecipeKey<String> RESEARCH = StringComponent.STRING.instance().otherKey("research").alt("key");
|
||||
static final RecipeKey<ItemStack> RESULT = ItemStackComponent.ITEM_STACK.instance().outputKey("result");
|
||||
static final RecipeKey<AspectList> ASPECTS = AspectListComponent.INSTANCE.otherKey("aspects").alt("aspect");
|
||||
static final RecipeKey<Integer> INSTABILITY = NumberComponent.INT.otherKey("instability");
|
||||
|
||||
// 注魔
|
||||
static final RecipeKey<ItemStack> CENTRAL = ItemStackComponent.ITEM_STACK.instance().inputKey("central");
|
||||
static final RecipeKey<List<ItemStack>> COMPONENTS = ItemStackComponent.ITEM_STACK.instance().asListOrSelf().inputKey("components");
|
||||
|
||||
// 坩埚:催化剂接受任意对象(脚本传物品 id 字符串或 ItemStack,由自定义组件解析)
|
||||
static final RecipeKey<Object> CATALYST = ObjectCatalystComponent.INSTANCE.otherKey("catalyst");
|
||||
|
||||
// 奥术有序
|
||||
static final RecipeKey<List<String>> PATTERN = StringComponent.STRING.instance().asList().otherKey("pattern");
|
||||
static final RecipeKey<dev.latvian.mods.kubejs.util.TinyMap<Character, ItemStack>> KEY =
|
||||
ItemStackComponent.ITEM_STACK.instance().asPatternKey().otherKey("key");
|
||||
|
||||
// 奥术无序
|
||||
static final RecipeKey<List<ItemStack>> INGREDIENTS = ItemStackComponent.ITEM_STACK.instance().asListOrSelf().inputKey("ingredients");
|
||||
|
||||
/** 坩埚催化剂组件:接受任意脚本对象,桥接时转换为 TC 期望的 Object(ItemStack/TagKey/Ingredient) */
|
||||
static final class ObjectCatalystComponent implements RecipeComponent<Object> {
|
||||
static final ObjectCatalystComponent INSTANCE = new ObjectCatalystComponent();
|
||||
private static final RecipeComponentType<?> TYPE = RecipeComponentType.unit(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "object_catalyst"), INSTANCE);
|
||||
|
||||
@Override public RecipeComponentType<?> type() { return TYPE; }
|
||||
@Override public com.mojang.serialization.Codec<Object> codec() {
|
||||
return com.mojang.serialization.Codec.STRING.xmap(
|
||||
s -> s, o -> String.valueOf(o));
|
||||
}
|
||||
@Override public dev.latvian.mods.rhino.type.TypeInfo typeInfo() {
|
||||
return dev.latvian.mods.rhino.type.TypeInfo.of(Object.class);
|
||||
}
|
||||
@Override public Object wrap(dev.latvian.mods.kubejs.recipe.RecipeScriptContext cx, Object from) {
|
||||
return from;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRecipeComponents(RecipeComponentTypeRegistry registry) {
|
||||
registry.register(AspectListComponent.INSTANCE.type());
|
||||
registry.register(ObjectCatalystComponent.TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRecipeSchemas(RecipeSchemaRegistry registry) {
|
||||
var tc = registry.namespace("thaumcraft");
|
||||
// typeOverride 指向真实存在的 serializer:TC 配方不走原版 RecipeManager,
|
||||
// 但 KubeJS 创建配方对象时要求 serializer 存在于 BuiltInRegistries.RECIPE_SERIALIZER,
|
||||
// 否则抛 "Serializer for type X is not found"。桥接后输出占位 JSON,故用 minecraft:crafting_shapeless。
|
||||
var vanillaType = ResourceLocation.withDefaultNamespace("crafting_shapeless");
|
||||
|
||||
tc.register("infusion", new RecipeSchema(
|
||||
RESEARCH, RESULT, INSTABILITY, ASPECTS, CENTRAL, COMPONENTS)
|
||||
.typeOverride(vanillaType)
|
||||
.factory(new KubeRecipeFactory(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "infusion"),
|
||||
InfusionRecipeKJS.class, InfusionRecipeKJS::new)));
|
||||
|
||||
tc.register("crucible", new RecipeSchema(
|
||||
RESEARCH, RESULT, CATALYST, ASPECTS)
|
||||
.typeOverride(vanillaType)
|
||||
.factory(new KubeRecipeFactory(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "crucible"),
|
||||
CrucibleRecipeKJS.class, CrucibleRecipeKJS::new)));
|
||||
|
||||
tc.register("arcane", new RecipeSchema(
|
||||
RESEARCH, RESULT, ASPECTS, PATTERN, KEY)
|
||||
.typeOverride(vanillaType)
|
||||
.factory(new KubeRecipeFactory(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "arcane"),
|
||||
ShapedArcaneRecipeKJS.class, ShapedArcaneRecipeKJS::new)));
|
||||
|
||||
tc.register("arcane_shapeless", new RecipeSchema(
|
||||
RESEARCH, RESULT, ASPECTS, INGREDIENTS)
|
||||
.typeOverride(vanillaType)
|
||||
.factory(new KubeRecipeFactory(
|
||||
ResourceLocation.fromNamespaceAndPath("tckubejsbridge", "arcane_shapeless"),
|
||||
ShapelessArcaneRecipeKJS.class, ShapelessArcaneRecipeKJS::new)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBindings(BindingRegistry bindings) {
|
||||
bindings.add("thaumcraft", new ThaumcraftBindings());
|
||||
}
|
||||
|
||||
/** 脚本侧绑定:event.thaumcraft.addObjectTag(...) / removeInfusion(...) 等 */
|
||||
public static final class ThaumcraftBindings {
|
||||
public void addObjectTag(ItemStack item, AspectList aspects) {
|
||||
if (item == null || item.isEmpty()) {
|
||||
ConsoleJS.SERVER.error("TC-KubeJS-Bridge: addObjectTag got empty item");
|
||||
return;
|
||||
}
|
||||
thaumcraft.api.ThaumcraftApi.registerObjectTag(item, aspects);
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: registered object tag for " + item);
|
||||
}
|
||||
|
||||
/** 按 research 键移除 TC 已有注魔配方(先删后注册 = 真正替换) */
|
||||
public boolean removeInfusion(String research) {
|
||||
return TCRecipeBridge.removeInfusion(research);
|
||||
}
|
||||
|
||||
/** 按 research 键移除 TC 已有奥术(有序/无序)配方 */
|
||||
public boolean removeArcane(String research) {
|
||||
return TCRecipeBridge.removeArcane(research);
|
||||
}
|
||||
|
||||
/** 清空去重集合(/reload 时若需重新注册可调用) */
|
||||
public void clearRegistered() {
|
||||
TCRecipeBridge.clearRegistered();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.latvian.mods.kubejs.script.ConsoleJS;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import thaumcraft.api.ThaumcraftApi;
|
||||
import thaumcraft.api.aspects.AspectList;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 桥接工具:将 KubeJS 脚本中解析出的配方参数实际注册到 Thaumcraft。
|
||||
*
|
||||
* 注意:TC 注魔/炼金/奥术配方全部走 {@link ThaumcraftApi#getCraftingRecipes()} 内存列表,
|
||||
* 不会进入原版 RecipeManager;因此本类在脚本执行时直接调用 TC API 完成注册,
|
||||
* 并生成一个无害的占位 JSON(避免 KubeJS 把空配方交给 RecipeManager 时报错)。
|
||||
*/
|
||||
public final class TCRecipeBridge {
|
||||
|
||||
private static final Set<String> REGISTERED_RESEARCH = new HashSet<>();
|
||||
|
||||
private TCRecipeBridge() {
|
||||
}
|
||||
|
||||
/** 按研究键去重,防止 /reload 重复注册 */
|
||||
private static boolean alreadyRegistered(String research) {
|
||||
return !REGISTERED_RESEARCH.add(research);
|
||||
}
|
||||
|
||||
/** 清空去重集合(供 KubeJS 事件在 /reload 时调用,允许重新注册) */
|
||||
public static void clearRegistered() {
|
||||
REGISTERED_RESEARCH.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 research 键从 TC 配方列表中移除已有的注魔配方。
|
||||
* 用于脚本"先删原配方,再注册新配方",实现真正替换。
|
||||
*
|
||||
* @return 是否至少移除了一条
|
||||
*/
|
||||
public static boolean removeInfusion(String research) {
|
||||
boolean removed = ThaumcraftApi.getCraftingRecipes().removeIf(r ->
|
||||
r instanceof thaumcraft.api.crafting.InfusionRecipe ir && research.equals(ir.research()));
|
||||
if (removed) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: removed infusion recipe(s) for research '" + research + "'");
|
||||
}
|
||||
// 允许该键后续重新注册
|
||||
REGISTERED_RESEARCH.remove(research);
|
||||
return removed;
|
||||
}
|
||||
|
||||
/** 按 research 键从 TC 配方列表中移除已有的奥术(有序/无序)配方 */
|
||||
public static boolean removeArcane(String research) {
|
||||
boolean removed = ThaumcraftApi.getCraftingRecipes().removeIf(r -> {
|
||||
if (r instanceof thaumcraft.api.crafting.ShapedArcaneRecipe sr) return research.equals(sr.research);
|
||||
if (r instanceof thaumcraft.api.crafting.ShapelessArcaneRecipe slr) return research.equals(slr.research);
|
||||
return false;
|
||||
});
|
||||
if (removed) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: removed arcane recipe(s) for research '" + research + "'");
|
||||
}
|
||||
// 允许该键后续重新注册
|
||||
REGISTERED_RESEARCH.remove(research);
|
||||
return removed;
|
||||
}
|
||||
|
||||
public static void registerInfusion(String research, ItemStack result, int instability,
|
||||
AspectList aspects, ItemStack central, ItemStack... components) {
|
||||
if (alreadyRegistered(research)) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: infusion recipe '" + research + "' already registered, skipping");
|
||||
return;
|
||||
}
|
||||
ThaumcraftApi.addInfusionCraftingRecipe(research, result, instability, aspects, central, components);
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: registered infusion recipe '" + research + "'");
|
||||
}
|
||||
|
||||
public static void registerCrucible(String research, ItemStack result, Object catalyst, AspectList aspects) {
|
||||
if (alreadyRegistered(research)) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: crucible recipe '" + research + "' already registered, skipping");
|
||||
return;
|
||||
}
|
||||
ThaumcraftApi.addCrucibleRecipe(research, result, catalyst, aspects);
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: registered crucible recipe '" + research + "'");
|
||||
}
|
||||
|
||||
public static void registerArcane(String research, ItemStack result, AspectList aspects, Object... recipe) {
|
||||
if (alreadyRegistered(research)) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: arcane recipe '" + research + "' already registered, skipping");
|
||||
return;
|
||||
}
|
||||
ThaumcraftApi.addArcaneCraftingRecipe(research, result, aspects, recipe);
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: registered arcane recipe '" + research + "'");
|
||||
}
|
||||
|
||||
public static void registerArcaneShapeless(String research, ItemStack result, AspectList aspects, Object... recipe) {
|
||||
if (alreadyRegistered(research)) {
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: arcane shapeless recipe '" + research + "' already registered, skipping");
|
||||
return;
|
||||
}
|
||||
ThaumcraftApi.addShapelessArcaneCraftingRecipe(research, result, aspects, recipe);
|
||||
ConsoleJS.SERVER.info("TC-KubeJS-Bridge: registered arcane shapeless recipe '" + research + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成无害占位配方 JSON,替换 KubeJS 对自定义配方自动生成的 JSON,
|
||||
* 使 RecipeManager 能正常解析但不会产生任何实际效果。
|
||||
*/
|
||||
public static JsonObject placeholderRecipe() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("type", "minecraft:crafting_shapeless");
|
||||
json.add("ingredients", new com.google.gson.JsonArray());
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("item", "minecraft:air");
|
||||
json.add("result", result);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package tckubejsbridge.kubejs;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.latvian.mods.kubejs.recipe.KubeRecipe;
|
||||
|
||||
/**
|
||||
* TC 配方 KubeRecipe 基类。
|
||||
*
|
||||
* 覆写 {@link #serializeChanges()}:脚本执行完成后(值已全部设置)调用子类实现的
|
||||
* {@link #bridgeToTC()} 完成 TC 内存配方注册,并用无害占位 JSON 替换序列化输出,
|
||||
* 避免 KubeJS 将自定义配方误交给原版 RecipeManager 后产生异常配方。
|
||||
*/
|
||||
public abstract class TCRecipeKJS extends KubeRecipe {
|
||||
|
||||
public TCRecipeKJS() {
|
||||
}
|
||||
|
||||
/** 由子类实现:读取 RecipeKey 值并调用 TC API 注册配方 */
|
||||
protected abstract void bridgeToTC();
|
||||
|
||||
@Override
|
||||
public KubeRecipe serializeChanges() {
|
||||
if (newRecipe || hasChanged()) {
|
||||
bridgeToTC();
|
||||
// 占位 JSON,避免污染 RecipeManager
|
||||
this.json = TCRecipeBridge.placeholderRecipe();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user