From 1cb9149f134cff876c18913f0494ccfd744370c2 Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Thu, 27 Aug 2026 01:18:01 +0800 Subject: [PATCH 1/3] generic registry event (actually only API) --- .../registry/base/RegistryEventJS.java | 52 ++++++++++ .../wrapper/registry/base/RegistryInfo.java | 31 ++++++ .../wrapper/registry/base/RegistryInfos.java | 94 +++++++++++++++++++ .../registry/base/RegistryObjectBuilder.java | 20 ++++ .../registry/base/RegistryObjectType.java | 17 ++++ .../base/RegistryObjectTypeRegistry.java | 29 ++++++ 6 files changed, 243 insertions(+) create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfo.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java new file mode 100644 index 00000000..8adfc50f --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java @@ -0,0 +1,52 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import com.tkisor.nekojs.NekoJS; +import net.minecraft.resources.Identifier; +import org.jspecify.annotations.NonNull; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * @author ZZZank + */ +public class RegistryEventJS { + private final RegistryInfo info; + private final Map> types; + private final Map> providers = new LinkedHashMap<>(); + + public RegistryEventJS( + RegistryInfo info, + Map> types + ) { + this.info = info; + this.types = types; + } + + public RegistryObjectBuilder custom(String id, String type) { + var builder = Objects.requireNonNull(types.get(type), "No registry object type matching " + type) + .createBuilder(createNewId(id)); + providers.put(builder.id, builder::build); + return builder; + } + + private @NonNull Identifier createNewId(String id) { + var identifier = id.contains(":") ? Identifier.parse(id) : Identifier.fromNamespaceAndPath(NekoJS.MODID, id); + if (providers.containsKey(identifier)) { + throw new IllegalArgumentException("id already present: " + identifier); + } + return identifier; + } + + public void register(String id, Supplier provider) { + var identifier = createNewId(id); + providers.put(identifier, provider); + } + + public Map> viewProviders() { + return Collections.unmodifiableMap(providers); + } +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfo.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfo.java new file mode 100644 index 00000000..06d40698 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfo.java @@ -0,0 +1,31 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +import java.lang.reflect.Type; +import java.util.Optional; + +/** + * @author ZZZank + */ +public record RegistryInfo( + Class objectBaseType, + Type rawType, + ResourceKey> resourceKey +) { + public Optional> getRegistry(RegistryAccess registryAccess) { + return registryAccess.lookup(resourceKey); + } + + public Registry getRegistryOrThrow(RegistryAccess registryAccess) { + var key = resourceKey; + return registryAccess.lookup(key).orElseThrow(() -> new IllegalStateException("No registry found for: " + key.identifier())); + } + + public Identifier identifier() { + return resourceKey.identifier(); + } +} \ No newline at end of file diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java new file mode 100644 index 00000000..ee782e30 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java @@ -0,0 +1,94 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.core.Registry; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.util.*; + +/** + * @author ZZZank + */ +public class RegistryInfos { + private final Map> infos = new HashMap<>(); + + /// @param classesToScan a list of classes, whose element will have its fields scanned and look for public static + /// final [ResourceKey] fields that represents a [Registry]. The [net.minecraft.core.registries.Registries] class + /// is a good example. + /// @param additionalInfos additional [RegistryInfo]s to be registered, usually from manual registration + public RegistryInfos(Set> classesToScan, Collection> additionalInfos) { + for (var c : classesToScan) { + for (var info : scanFromClass(c)) { + infos.put(info.resourceKey().identifier(), info); + } + } + for (var info : additionalInfos) { + infos.put(info.resourceKey().identifier(), info); + } + } + + @SuppressWarnings("unchecked") + public RegistryInfo get(ResourceKey> key) { + return (RegistryInfo) infos.get(key.identifier()); + } + + public Map> view() { + return Collections.unmodifiableMap(infos); + } + + // example: + // public static final ResourceKey> BLOCK = createRegistryKey("block"); + private ArrayList> scanFromClass(Class c) { + var result = new ArrayList>(); + for (var field : c.getDeclaredFields()) { + var modifiers = field.getModifiers(); + if ( + Modifier.isPublic(modifiers) + && Modifier.isStatic(modifiers) + && Modifier.isFinal(modifiers) + // ResourceKey + && field.getType() == ResourceKey.class + // ResourceKey + && field.getGenericType() instanceof ParameterizedType parameterizedKey + // ResourceKey> + && parameterizedKey.getActualTypeArguments()[0] instanceof ParameterizedType parameterizedReg + // ResourceKey> + && parameterizedReg.getRawType() == Registry.class + ) { + // the ? in ResourceKey> + var rawKeyType = parameterizedReg.getActualTypeArguments()[0]; + + // there multiple possibilities of Type: + // - Class: Block, String[], List + // - GenericArrayType: T[] + // - ParameterizedType: Codec + // - TypeVariable: T, K + // - WildcardType: ?, ? extends String + // Only Class and ParameterizedType makes sense for registry key + Class keyType; + if (rawKeyType instanceof Class keyTypeClass) { + keyType = keyTypeClass; + } else if (rawKeyType instanceof ParameterizedType keyTypeParameterized) { + keyType = (Class) keyTypeParameterized.getRawType(); + } else { + continue; + } + + ResourceKey key; + try { + key = (ResourceKey) field.get(null); + } catch (Exception _) { + continue; + } + + @SuppressWarnings("unchecked") + var built = new RegistryInfo<>((Class) keyType, rawKeyType, (ResourceKey>) key); + result.add(built); + } + } + + return result; + } +} \ No newline at end of file diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java new file mode 100644 index 00000000..216ce86e --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java @@ -0,0 +1,20 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.resources.Identifier; + +/** + * + * + * @author ZZZank + */ +public abstract class RegistryObjectBuilder { + protected final RegistryInfo info; + public final Identifier id; + + public RegistryObjectBuilder(RegistryInfo info, Identifier id) { + this.info = info; + this.id = id; + } + + public abstract T build(); +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java new file mode 100644 index 00000000..57eeb127 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java @@ -0,0 +1,17 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.core.Registry; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +/** + * @author ZZZank + */ +public interface RegistryObjectType { + + ResourceKey> resourceKey(); + + String type(); + + RegistryObjectBuilder createBuilder(RegistryInfo info, Identifier id); +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java new file mode 100644 index 00000000..eab4dc68 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java @@ -0,0 +1,29 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.core.Registry; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; + +import java.util.Map; +import java.util.function.BiFunction; + +/** + * @author ZZZank + */ +public interface RegistryObjectTypeRegistry { + Scope scope(ResourceKey> key); + + void register(RegistryObjectType type); + + /// [RegistryEventJS] will use values here, aka `Map>` + Map>, Map>> view(); + + interface Scope extends AutoCloseable { + ResourceKey> key(); + + void register(String type, BiFunction, Identifier, RegistryObjectBuilder> factory); + + @Override + void close(); + } +} From 8b9ba6bde35c693874414a688985379dc78afb99 Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Thu, 27 Aug 2026 12:58:46 +0800 Subject: [PATCH 2/3] allow callback on extension point finish --- .../nekojs/core/plugin/NekoPluginBootstrap.java | 7 +++++-- .../core/plugin/NekoPluginExtensionPoint.java | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginBootstrap.java b/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginBootstrap.java index 4d7b458e..f1f3881c 100644 --- a/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginBootstrap.java +++ b/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginBootstrap.java @@ -154,10 +154,13 @@ private static void collectExtensions(List plugins, BootstrapState } } List> extensionPoints = registry.freeze(); - for (NekoJSPlugin plugin : plugins) { - for (NekoPluginExtensionPoint extensionPoint : extensionPoints) { + for (NekoPluginExtensionPoint extensionPoint : extensionPoints) { + for (NekoJSPlugin plugin : plugins) { extensionPoint.collect(plugin, state); } + if (extensionPoint.onFinish() != null) { + extensionPoint.onFinish().run(); + } } // 内置 backend 经 NekoProbeBuiltinPlugin 走同一扩展点注册;这里只负责装配收尾 diff --git a/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginExtensionPoint.java b/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginExtensionPoint.java index 570dfe17..8de6c652 100644 --- a/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginExtensionPoint.java +++ b/common/src/main/java/com/tkisor/nekojs/core/plugin/NekoPluginExtensionPoint.java @@ -30,7 +30,8 @@ public record NekoPluginExtensionPoint

( String id, Class

pluginType, Predicate enabled, - BiConsumer collector + BiConsumer collector, + Runnable onFinish ) { public NekoPluginExtensionPoint { if (id == null || id.isBlank()) { @@ -50,7 +51,16 @@ public static

NekoPluginExtensionPoint

of( Class

pluginType, BiConsumer collector ) { - return new NekoPluginExtensionPoint<>(id, pluginType, context -> true, collector); + return new NekoPluginExtensionPoint<>(id, pluginType, context -> true, collector, null); + } + + public static

NekoPluginExtensionPoint

of( + String id, + Class

pluginType, + BiConsumer collector, + Runnable onFinish + ) { + return new NekoPluginExtensionPoint<>(id, pluginType, context -> true, collector, onFinish); } /** @@ -63,7 +73,7 @@ public static

NekoPluginExtensionPoint

clientOnly( Class

pluginType, BiConsumer collector ) { - return new NekoPluginExtensionPoint<>(id, pluginType, NekoPluginExtensionContext::client, collector); + return new NekoPluginExtensionPoint<>(id, pluginType, NekoPluginExtensionContext::client, collector, null); } /** From 7c2fce53929bc6b6660f404459ebfca5a88c3abd Mon Sep 17 00:00:00 2001 From: ZZZank <47418975+ZZZank@users.noreply.github.com> Date: Thu, 27 Aug 2026 14:32:00 +0800 Subject: [PATCH 3/3] i give up --- docs/MINECRAFT_REGISTRY.md | 218 ++++++++++++++++++ .../registry/base/RegistryEventJS.java | 11 +- .../wrapper/registry/base/RegistryInfos.java | 20 ++ .../registry/base/RegistryObjectBuilder.java | 2 +- .../registry/base/RegistryObjectType.java | 26 ++- .../base/RegistryObjectTypeRegistry.java | 72 ++++++ .../registry/base/RegistryObjectTypes.java | 40 ++++ .../registry/base/impl/ItemBuilderJS.java | 90 ++++++++ .../base/plugin/BuiltinRegistrySupport.java | 25 ++ .../base/plugin/RegistryInfosPlugin.java | 60 +++++ .../base/plugin/RegistryInfosRegistry.java | 81 +++++++ .../base/plugin/RegistrySupportPlugin.java | 16 ++ 12 files changed, 657 insertions(+), 4 deletions(-) create mode 100644 docs/MINECRAFT_REGISTRY.md create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypes.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/impl/ItemBuilderJS.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/BuiltinRegistrySupport.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosPlugin.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosRegistry.java create mode 100644 platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistrySupportPlugin.java diff --git a/docs/MINECRAFT_REGISTRY.md b/docs/MINECRAFT_REGISTRY.md new file mode 100644 index 00000000..c5c6af25 --- /dev/null +++ b/docs/MINECRAFT_REGISTRY.md @@ -0,0 +1,218 @@ + +# Minecraft 通用注册表事件系统 + +本文档描述 NekoJS 的通用注册表事件系统设计与实现。 + +--- + +## 1. 现状概览 + +### 1.1 已完成部分(neoforge-26.2) + +| 模块 | 说明 | +|------|------| +| `RegistryInfo` | 注册表元信息(基础类型、原始类型、资源键) | +| `RegistryInfos` | 全局注册表信息管理器,支持反射扫描与手动添加 | +| `RegistryObjectBuilder` | 注册表对象构建器抽象基类 | +| `RegistryObjectType` | 注册表对象类型(类型标识 + 工厂函数) | +| `RegistryObjectTypes` | 按注册表分组的类型集合 | +| `RegistryObjectTypeRegistry` | 类型注册管理器(Scope 模式) | +| `RegistryEventJS` | JavaScript 事件包装,支持 `custom()` / `register()` | + +插件系统: +| 类 | 说明 | +|----|------| +| `RegistrySupportPlugin` | 插件接口(`registerRegistryInfo` + `registerRegistryObjectType`) | +| `RegistryInfosPlugin` | 扩展点提供者,协调两阶段初始化 | +| `RegistryInfosRegistry` | 收集扫描类和额外信息 | +| `BuiltinRegistrySupport` | 内置支持(扫描 `Registries.class`,注册 Item 类型) | + +已实现的 Builder: +- `ItemBuilderJS`(基础的物品构建器,支持 stackSize/damage/fireResistant/rarity/food/burning 等) + +### 1.2 待完成部分 + +1. 跨平台改造:当前仅在 neoforge-26.2,需扩展到其他版本 + - RegistryEventJS应当避免引用neoforge的RegisterEvent,而 +2. 更多 Builder 实现: + - Item的更多变体,比如axe/hoe/shovel等工具,helmet/chestplate等装甲 + - `BlockBuilderJS`(方块构建器) + - `FluidBuilderJS`(流体构建器) + - 其他注册表类型(EntityType、Potion、Enchantment 等) +3. 事件绑定:与 NeoForge `RegisterEvent` 的集成 +4. 处理“连带注册”,比如注册Block时,若无明确要求,也需要注册BlockItem;注册Fluid同时也可能还需要注册:流体方块,流动的流体方块,流体桶。 + +处理“连带注册”的一些思路:添加`builder.additionalBuilders()`提供其他需要注册的builder,在事件发布完成之后与直接注册的builder一起处理;添加`builder.handleAdditionalRegistry(Consumer)`,也是在发布完成后处理 + +--- + +## 2. 扩展点相关 + +### 2.1 注册系统使用流程(两阶段插件加载) + +``` +1. nekojs:registry_infos 扩展点 + → 各 RegistrySupportPlugin.registerRegistryInfo() + → 收集 classesToScan + additionalInfos + → finish 回调 → 构建 RegistryInfos 实例 + +2. nekojs:registry_object_types 扩展点 + → 各 RegistrySupportPlugin.registerRegistryObjectType() + → 通过 Scope 注册 RegistryObjectType + → finish 回调 → 构建 RegistryObjectTypes 不可变映射 +``` + +### 2.2 暴露的问题与改进方向 + +注册表事件系统尝试通过扩展点来减少hack,但是总体使用体验非常糟糕,也暴露了一些设计问题: +1. 扩展点目前可以插桩的位置太少,只有NekoJSPlugin.init()提供了一个所有扩展点都完成加载之后的时机。这个注册系统在设计时registry_object_types依赖于registry_infos的结果(registry.build()得到RegistryInfos),特意添加了onFinish callback,这才勉强可用。 +2. 扩展点存在前后依赖或者需要的情况难以处理。目前registry_infos扩展点是提前初始化registry的实例,并在自己的onFinish里初始化另一个扩展点的registry。一个扩展点需要的资源在需要之前就初始化了,同时又干涉另一个扩展点的初始化。 +3. 内置扩展点高度依赖于NekoPluginExtensionContext,一旦需要添加内置扩展点就需要修改NekoPluginExtensionContext,进而影响runtime、bootstrap等等,反而显得耦合严重。 + +总得来讲,扩展点需要改造,应当提供一个类似于stream Collector的initializer/merger/finisher系统: + +```java +void loadExt(ExtensionPoint ext) { + var intermediate = ext.initializer().get(); + for (var plugin: plugins) { + if (ext.shouldExableFor(plugin)) { + ext.merger.apply(intermediate, plugin) + } + } + ext.result = new SomeKindOfHolder<>(ext, ext.finisher().apply(intermediate)); +} +``` + +扩展点注册之后应当返回一个持有扩展点,并可以获取加载结果的对象。 + +同时应当允许扩展点在initializer里使用另一个扩展点的结果,此外允许finisher仅接受结果(以下仅为示例): + +```java +Supplier buildInitializer(ExtensionPoint another, Func action) { + return () -> action.apply(another.resultOrThrow()); +} + +Function noTransformFinisher(Consumer action) { + return (value) -> { + action.accespt(result); + return result; + } +} +``` + +--- + +## 3. 语法约束与设计决策 + +### 3.1 Builder 模式使用 public field + +```java +// ✅ 推荐:使用 public field +public class ItemBuilderJS extends RegistryObjectBuilder { + public int maxStackSize = 64; + public Rarity rarity = Rarity.COMMON; + // ... +} + +// ❌ 不推荐:链式调用方法 +public class ItemBuilderJS extends RegistryObjectBuilder { + public ItemBuilderJS maxStackSize(int size) { ... return this; } + public ItemBuilderJS rarity(Rarity r) { ... return this; } +} +``` + +原因:方法返回类型是固定类型而非 `this`,涉及子类时返回类型无法正确表示自身。 + +### 3.2 Scope 模式注册类型 + +```java +// 推荐方式:使用 try-with-resources +try (var scope = registry.scope(Registries.ITEM)) { + scope.register("basic", ItemBuilderJS::new); +} + +// 另一种方式:直接注册 RegistryObjectType +registry.register(new RegistryObjectType.Impl<>(...)); +``` + +### 3.3 不可变集合 + +```java +// 最终输出使用不可变集合 +public Map> viewProviders() { + return Collections.unmodifiableMap(providers); +} +``` + +--- + +## 4. API 使用示例 + +### 4.1 脚本端使用 + +```javascript +// 注册表事件处理 +RegistryEvents.register((event) => { + // 使用内置类型创建 + event.custom("my_item", "basic", (builder) => { + builder.maxStackSize = 16; + builder.rarity = "RARE"; + }); + + // 直接注册 Supplier + event.register("my_custom_item", () => { + return new MyCustomItem(); + }); +}); +``` + +### 4.2 插件开发 + +```java +@RegisterNekoJSPlugin +public class MyRegistryPlugin implements RegistrySupportPlugin { + + @Override + public void registerRegistryInfo(RegistryInfosRegistry registry) { + // 添加需要扫描的类 + registry.addClassesToScan(Registries.class); + + // 或手动添加 + registry.addAdditionalInfo(MyCustom.class, MY_CUSTOM_KEY); + } + + @Override + public void registerRegistryObjectType(RegistryObjectTypeRegistry registry) { + try (var scope = registry.scope(Registries.ITEM)) { + scope.register("my_type", MyItemBuilder::new); + } + } +} +``` + +### 4.3 自定义 Builder + +```java +public class MyItemBuilder extends RegistryObjectBuilder { + public String customProperty = "default"; + + public MyItemBuilder(RegistryInfo info, Identifier id) { + super(info, id); + } + + @Override + public MyItem build() { + return new MyItem(id, customProperty); + } +} +``` + +--- + +## 5. 相关文件 + +| 文件 | 说明 | +|------|------| +| `platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/` | 核心实现 | +| `platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/impl/` | Builder 实现 | +| `platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/` | 插件系统 | \ No newline at end of file diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java index 8adfc50f..827142cd 100644 --- a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryEventJS.java @@ -8,6 +8,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -27,12 +28,18 @@ public RegistryEventJS( } public RegistryObjectBuilder custom(String id, String type) { - var builder = Objects.requireNonNull(types.get(type), "No registry object type matching " + type) - .createBuilder(createNewId(id)); + var objectType = Objects.requireNonNull(types.get(type), "No registry object type matching " + type); + var builder = objectType.createBuilder(createNewId(id)); providers.put(builder.id, builder::build); return builder; } + public RegistryObjectBuilder custom(String id, String type, Consumer> modifner) { + var builder = custom(id, type); + modifner.accept(builder); + return builder; + } + private @NonNull Identifier createNewId(String id) { var identifier = id.contains(":") ? Identifier.parse(id) : Identifier.fromNamespaceAndPath(NekoJS.MODID, id); if (providers.containsKey(identifier)) { diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java index ee782e30..3dcb4f91 100644 --- a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryInfos.java @@ -12,8 +12,28 @@ * @author ZZZank */ public class RegistryInfos { + private static RegistryInfos INSTANCE; + private final Map> infos = new HashMap<>(); + /** + * 获取全局 RegistryInfos 实例。 + * + * @return 全局实例,如果尚未初始化则返回 null + */ + public static RegistryInfos getInstance() { + return INSTANCE; + } + + /** + * 设置全局 RegistryInfos 实例。 + * + * @param instance RegistryInfos 实例 + */ + public static void setInstance(RegistryInfos instance) { + INSTANCE = instance; + } + /// @param classesToScan a list of classes, whose element will have its fields scanned and look for public static /// final [ResourceKey] fields that represents a [Registry]. The [net.minecraft.core.registries.Registries] class /// is a good example. diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java index 216ce86e..f713adb8 100644 --- a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectBuilder.java @@ -3,7 +3,7 @@ import net.minecraft.resources.Identifier; /** - * + * 对于所有的实现类,可修改的属性都建议使用public的field 而 不 是 通过返回自身实现链式调用的方法,因为方法返回类型不是“this”而是一个固定类型,一旦涉及到子类,返回类型就不能有效表示自身 * * @author ZZZank */ diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java index 57eeb127..07615513 100644 --- a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectType.java @@ -3,6 +3,10 @@ import net.minecraft.core.Registry; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Objects; +import java.util.function.BiFunction; /** * @author ZZZank @@ -11,7 +15,27 @@ public interface RegistryObjectType { ResourceKey> resourceKey(); + RegistryInfo registryInfo(); + String type(); - RegistryObjectBuilder createBuilder(RegistryInfo info, Identifier id); + RegistryObjectBuilder createBuilder(Identifier id); + + /** + * @author ZZZank + */ + @ApiStatus.Internal + record Impl( + ResourceKey> resourceKey, + RegistryInfo registryInfo, + String type, + BiFunction, Identifier, RegistryObjectBuilder> factory + ) implements RegistryObjectType { + + @Override + public RegistryObjectBuilder createBuilder(Identifier id) { + Objects.requireNonNull(id, "id"); + return factory.apply(registryInfo, id); + } + } } diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java index eab4dc68..0c4f6fe6 100644 --- a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypeRegistry.java @@ -3,8 +3,12 @@ import net.minecraft.core.Registry; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; +import org.jetbrains.annotations.ApiStatus; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; /** @@ -26,4 +30,72 @@ interface Scope extends AutoCloseable { @Override void close(); } + + /** + * @author ZZZank + */ + @ApiStatus.Internal + class Impl implements RegistryObjectTypeRegistry { + public final RegistryInfos registryInfos; + private final Map>, Map>> types = new LinkedHashMap<>(); + + public Impl(RegistryInfos registryInfos) { + this.registryInfos = Objects.requireNonNull(registryInfos, "registryInfos"); + } + + @Override + public Scope scope(ResourceKey> key) { + Objects.requireNonNull(key, "key"); + return new ScopeImpl<>(key); + } + + @Override + @SuppressWarnings("unchecked") + public void register(RegistryObjectType type) { + Objects.requireNonNull(type, "type"); + var key = (ResourceKey>) (ResourceKey) type.resourceKey(); + types.computeIfAbsent(key, k -> new HashMap<>()).put(type.type(), type); + } + + @Override + public Map>, Map>> view() { + return types; + } + + private class ScopeImpl implements Scope { + private final ResourceKey> key; + private final RegistryInfo registryInfo; + private final Map> scopeTypes; + + ScopeImpl(ResourceKey> key) { + this.key = key; + this.registryInfo = registryInfos.get(key); + + var existing = types.get(key); + this.scopeTypes = existing != null ? existing : new HashMap<>(); + + @SuppressWarnings("unchecked") + var castedKey = (ResourceKey>) (ResourceKey) key; + types.put(castedKey, scopeTypes); + } + + @Override + public ResourceKey> key() { + return key; + } + + @Override + public void register(String type, BiFunction, Identifier, RegistryObjectBuilder> factory) { + Objects.requireNonNull(type, "type"); + Objects.requireNonNull(factory, "factory"); + var objectType = new RegistryObjectType.Impl<>(key, registryInfo, type, factory); + scopeTypes.put(type, objectType); + } + + @Override + public void close() { + // AutoCloseable - no cleanup needed + } + } + } } diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypes.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypes.java new file mode 100644 index 00000000..599b2553 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/RegistryObjectTypes.java @@ -0,0 +1,40 @@ +package com.tkisor.nekojs.wrapper.registry.base; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import org.jetbrains.annotations.ApiStatus; + +import java.util.Map; + +/** + * @author ZZZank + */ +public interface RegistryObjectTypes { + @SuppressWarnings("unchecked") + static RegistryObjectTypes get(ResourceKey> key) { + return (RegistryObjectTypes) Internal.REGISTERED.get(key); + } + + RegistryInfo registryInfo(); + + RegistryObjectType get(String type); + + Map> view(); + + @ApiStatus.Internal + record Impl( + RegistryInfo registryInfo, + Map> view + ) implements RegistryObjectTypes { + + @Override + public RegistryObjectType get(String type) { + return view.get(type); + } + } + + @ApiStatus.Internal + abstract class Internal { + public static Map>, RegistryObjectTypes> REGISTERED; + } +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/impl/ItemBuilderJS.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/impl/ItemBuilderJS.java new file mode 100644 index 00000000..5b495e70 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/impl/ItemBuilderJS.java @@ -0,0 +1,90 @@ +package com.tkisor.nekojs.wrapper.registry.base.impl; + +import com.tkisor.nekojs.wrapper.registry.FoodBuilderJS; +import com.tkisor.nekojs.wrapper.registry.base.RegistryInfo; +import com.tkisor.nekojs.wrapper.registry.base.RegistryObjectBuilder; +import net.minecraft.core.component.DataComponents; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Rarity; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.entity.FuelValues; +import org.jspecify.annotations.NonNull; + +import java.util.function.Consumer; + +public class ItemBuilderJS extends RegistryObjectBuilder { + + public int maxStackSize = 64; + public int maxDamage = 0; + public boolean fireResistant = false; + public Rarity rarity = Rarity.COMMON; + public boolean glowing = false; + public int burnTime = 0; + public Identifier groupTab = null; + public Consumer food = null; + + public ItemBuilderJS(RegistryInfo info, Identifier id) { + super(info, id); + } + + @Override + public Item build() { + Item.Properties props = buildProperties(); + + // 仅在需要覆盖方法(发光/燃料)时用匿名子类,否则直接 new Item + if (!glowing && burnTime <= 0) { + return new Item(props); + } + + final boolean foil = glowing; + final int burn = burnTime; + return new Item(props) { + @Override + public boolean isFoil(@NonNull ItemStack stack) { + return foil; + } + + @Override + public int getBurnTime(@NonNull ItemStack stack, RecipeType type, @NonNull FuelValues fuelValues) { + return burn; + } + }; + } + + /** + * 构建配置好的 {@link Item.Properties}(含 id / stackSize / durability / fireResistant / + * rarity / 食物组件)。供 {@link net.minecraft.world.item.BlockItem} 等需要复用属性的 + * 场景调用(脚本通过 BlockBuilderJS.item(...) 配置 BlockItem 属性时走这里)。 + */ + public Item.Properties buildProperties() { + ResourceKey key = ResourceKey.create(Registries.ITEM, id); + Item.Properties props = new Item.Properties().setId(key); + + if (maxDamage > 0) { + props.durability(maxDamage); + } else { + props.stacksTo(maxStackSize); + } + + if (fireResistant) { + props.fireResistant(); + } + + if (rarity != Rarity.COMMON) { + props.rarity(rarity); + } + + if (food != null) { + var foodBuilder = new FoodBuilderJS(); + food.accept(foodBuilder); + props.food(foodBuilder.buildFood()); + props.component(DataComponents.CONSUMABLE, foodBuilder.buildConsumable()); + } + + return props; + } +} \ No newline at end of file diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/BuiltinRegistrySupport.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/BuiltinRegistrySupport.java new file mode 100644 index 00000000..dc58792c --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/BuiltinRegistrySupport.java @@ -0,0 +1,25 @@ +package com.tkisor.nekojs.wrapper.registry.base.plugin; + +import com.tkisor.nekojs.api.annotation.RegisterNekoJSPlugin; +import com.tkisor.nekojs.wrapper.registry.base.RegistryObjectTypeRegistry; +import com.tkisor.nekojs.wrapper.registry.base.impl.ItemBuilderJS; +import net.minecraft.core.registries.Registries; + +/** + * @author ZZZank + */ +@RegisterNekoJSPlugin +public class BuiltinRegistrySupport implements RegistrySupportPlugin { + + @Override + public void registerRegistryInfo(RegistryInfosRegistry registry) { + registry.addClassesToScan(Registries.class); + } + + @Override + public void registerRegistryObjectType(RegistryObjectTypeRegistry registry) { + try (var scope = registry.scope(Registries.ITEM)) { + scope.register("basic", ItemBuilderJS::new); + } + } +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosPlugin.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosPlugin.java new file mode 100644 index 00000000..5747f37c --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosPlugin.java @@ -0,0 +1,60 @@ +package com.tkisor.nekojs.wrapper.registry.base.plugin; + +import com.tkisor.nekojs.api.NekoJSPlugin; +import com.tkisor.nekojs.api.annotation.RegisterNekoJSPlugin; +import com.tkisor.nekojs.core.plugin.NekoPluginExtensionPoint; +import com.tkisor.nekojs.core.plugin.NekoPluginExtensionProvider; +import com.tkisor.nekojs.core.plugin.NekoPluginExtensionRegistry; +import com.tkisor.nekojs.wrapper.registry.base.RegistryInfos; +import com.tkisor.nekojs.wrapper.registry.base.RegistryObjectTypeRegistry; +import com.tkisor.nekojs.wrapper.registry.base.RegistryObjectTypes; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import org.apache.logging.log4j.util.Lazy; + +import java.util.HashMap; +import java.util.Map; + +/** + * 注册 RegistryInfos 扩展点的插件。 + * + * @author ZZZank + */ +@RegisterNekoJSPlugin +public class RegistryInfosPlugin implements NekoPluginExtensionProvider, NekoJSPlugin { + private RegistryInfosRegistry.Impl infosRegistry = new RegistryInfosRegistry.Impl(); + private RegistryObjectTypeRegistry.Impl typesRegistry; + + @Override + public void registerPluginExtensionPoints(NekoPluginExtensionRegistry registry) { + + registry.register(NekoPluginExtensionPoint.of( + "nekojs:registry_infos", + RegistrySupportPlugin.class, + (plugin, cx) -> plugin.registerRegistryInfo(infosRegistry), + () -> { + RegistryInfos.setInstance(infosRegistry.build()); + infosRegistry = null; + typesRegistry = new RegistryObjectTypeRegistry.Impl(RegistryInfos.getInstance()); + } + )); + registry.register(NekoPluginExtensionPoint.of( + "nekojs:registry_object_types", + RegistrySupportPlugin.class, + (plugin, cx) -> plugin.registerRegistryObjectType(typesRegistry), + () -> { + var registryInfos = typesRegistry.registryInfos; + + var map = new HashMap>, RegistryObjectTypes>(); + for (var entry : typesRegistry.view().entrySet()) { + ResourceKey> key = entry.getKey(); + @SuppressWarnings({"rawtypes", "unchecked"}) + var types = new RegistryObjectTypes.Impl(registryInfos.get((ResourceKey) key), Map.copyOf(entry.getValue())); + map.put(key, types); + } + + RegistryObjectTypes.Internal.REGISTERED = Map.copyOf(map); + } + )); + } +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosRegistry.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosRegistry.java new file mode 100644 index 00000000..5509d424 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistryInfosRegistry.java @@ -0,0 +1,81 @@ +package com.tkisor.nekojs.wrapper.registry.base.plugin; + +import com.tkisor.nekojs.wrapper.registry.base.RegistryInfo; +import com.tkisor.nekojs.wrapper.registry.base.RegistryInfos; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import org.jetbrains.annotations.ApiStatus; + +import java.util.*; + +/** + * @author ZZZank + */ +public interface RegistryInfosRegistry { + + /** + * 添加需要扫描的类。这些类的 {@code public static final ResourceKey>} 字段将被扫描以发现 Registry。 + * + * @param classes 需要扫描的类 + */ + void addClassesToScan(Collection> classes); + + void addClassesToScan(Class... classes); + + /** + * 添加额外的 RegistryInfo + * + * @param infos 额外的 RegistryInfo + */ + void addAdditionalInfos(Collection> infos); + + void addAdditionalInfos(RegistryInfo... infos); + + void addAdditionalInfo(Class clazz, ResourceKey> key); + + /** + * RegistryInfosRegister 的实现,收集 plugin 提供的 classesToScan 和 additionalInfos, + * 并在构建时创建 RegistryInfos 实例。 + */ + @ApiStatus.Internal + final class Impl implements RegistryInfosRegistry { + private final Set> classesToScan = new LinkedHashSet<>(); + private final List> additionalInfos = new ArrayList<>(); + + @Override + public void addClassesToScan(Collection> classes) { + classes.forEach(Objects::requireNonNull); + classesToScan.addAll(classes); + } + + @Override + public void addClassesToScan(Class... classes) { + addClassesToScan(Arrays.asList(classes)); + } + + @Override + public void addAdditionalInfos(Collection> infos) { + infos.forEach(Objects::requireNonNull); + additionalInfos.addAll(infos); + } + + @Override + public void addAdditionalInfos(RegistryInfo... infos) { + addAdditionalInfos(Arrays.asList(infos)); + } + + @Override + public void addAdditionalInfo(Class clazz, ResourceKey> key) { + addAdditionalInfos(new RegistryInfo<>(clazz, clazz, key)); + } + + /** + * 构建 RegistryInfos 实例。 + * + * @return 新的 RegistryInfos 实例 + */ + public RegistryInfos build() { + return new RegistryInfos(classesToScan, additionalInfos); + } + } +} diff --git a/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistrySupportPlugin.java b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistrySupportPlugin.java new file mode 100644 index 00000000..d7817a38 --- /dev/null +++ b/platforms/neoforge-26.2/src/main/java/com/tkisor/nekojs/wrapper/registry/base/plugin/RegistrySupportPlugin.java @@ -0,0 +1,16 @@ +package com.tkisor.nekojs.wrapper.registry.base.plugin; + +import com.tkisor.nekojs.api.NekoJSPlugin; +import com.tkisor.nekojs.wrapper.registry.base.RegistryObjectTypeRegistry; + +/** + * @author ZZZank + */ +public interface RegistrySupportPlugin extends NekoJSPlugin { + + default void registerRegistryInfo(RegistryInfosRegistry registry) { + } + + default void registerRegistryObjectType(RegistryObjectTypeRegistry registry) { + } +}