diff --git a/build.gradle b/build.gradle index d443d6fae..3b57c7008 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,7 @@ unifiedPublishing { releaseType = "${ENV.RELEASE_TYPE ?: 'release'}" gameVersions = [ rootProject.minecraft_version, - '1.20' + '1.21' ] gameLoaders = ["neoforge"] displayName = "$mod_name NeoForge $project.version" diff --git a/gradle.properties b/gradle.properties index 632e3d6b3..05fe746f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ neoforge_version=21.1.12 parchment_mc_version=1.21 parchment_mapping_version=2024.07.28 rhino_version=2100.2.5-build.46 -tiny_server_version=1.0.0-build.2 +tiny_server_version=1.0.0-build.3 architectury_version=13.0.6 rei_version=16.0.744 diff --git a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java index e4bbd392c..e0c982fd8 100644 --- a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java +++ b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java @@ -550,8 +550,8 @@ public void registerTypeWrappers(TypeWrapperRegistry registry) { registry.register(ListTag.class, (from, target) -> NBTUtils.isTagCollection(from), NBTUtils::toTagList); registry.register(Tag.class, NBTUtils::toTag); registry.register(DataComponentType.class, DataComponentWrapper::wrapType); - registry.register(DataComponentMap.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.mapOf(RegistryAccessContainer.of(cx), from)); - registry.register(DataComponentPatch.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.patchOf(RegistryAccessContainer.of(cx), from)); + registry.register(DataComponentMap.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.mapOf(RegistryAccessContainer.of(cx).nbt(), from)); + registry.register(DataComponentPatch.class, DataComponentWrapper::filter, (cx, from, target) -> DataComponentWrapper.patchOf(RegistryAccessContainer.of(cx).nbt(), from)); registry.register(BlockPos.class, KubeJSTypeWrappers::blockPosOf); registry.register(Vec3.class, KubeJSTypeWrappers::vec3Of); diff --git a/src/main/java/dev/latvian/mods/kubejs/KubeJS.java b/src/main/java/dev/latvian/mods/kubejs/KubeJS.java index ab43e274c..e750435c9 100644 --- a/src/main/java/dev/latvian/mods/kubejs/KubeJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/KubeJS.java @@ -111,7 +111,7 @@ public KubeJS(IEventBus bus, Dist dist, ModContainer mod) throws Throwable { } LOGGER.info("Loading vanilla registries..."); - RegistryType.init(); + RegistryType.Scanner.init(); var pluginTimer = Stopwatch.createStarted(); LOGGER.info("Looking for KubeJS plugins..."); diff --git a/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java b/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java index 82e17006d..234f7ba5b 100644 --- a/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java +++ b/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java @@ -46,8 +46,6 @@ static Path dir(Path dir) { Path PACKICON = CONFIG.resolve("packicon.png"); Path README = DIRECTORY.resolve("README.txt"); Path LOCAL = dir(GAMEDIR.resolve("local").resolve("kubejs")); - Path LOCAL_CACHE = dir(LOCAL.resolve("cache")); - Path LOCAL_WEB_IMG_CACHE = dir(LOCAL_CACHE.resolve("web/img")); Path LOCAL_DEV_PROPERTIES = LOCAL.resolve("dev.json"); Path EXPORT = dir(LOCAL.resolve("export")); Path EXPORTED_PACKS = dir(LOCAL.resolve("exported_packs")); diff --git a/src/main/java/dev/latvian/mods/kubejs/bindings/RegistryWrapper.java b/src/main/java/dev/latvian/mods/kubejs/bindings/RegistryWrapper.java index 2acf7f049..2a41c189c 100644 --- a/src/main/java/dev/latvian/mods/kubejs/bindings/RegistryWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/bindings/RegistryWrapper.java @@ -25,6 +25,10 @@ public static RegistryWrapper of(Context cx, ResourceLocation id) { return RegistryAccessContainer.of(cx).wrapRegistry(id); } + public static RegistryAccessContainer access() { + return RegistryAccessContainer.current; + } + public T get(ResourceLocation id) { return registry.get(id); } diff --git a/src/main/java/dev/latvian/mods/kubejs/bindings/SizedIngredientWrapper.java b/src/main/java/dev/latvian/mods/kubejs/bindings/SizedIngredientWrapper.java index 486c2309f..4aa9e2d32 100644 --- a/src/main/java/dev/latvian/mods/kubejs/bindings/SizedIngredientWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/bindings/SizedIngredientWrapper.java @@ -6,6 +6,7 @@ import dev.latvian.mods.kubejs.typings.Info; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.rhino.type.TypeInfo; +import dev.latvian.mods.rhino.util.HideFromJS; import net.minecraft.tags.TagKey; import net.minecraft.util.Mth; import net.minecraft.world.item.Item; @@ -37,6 +38,7 @@ static SizedIngredient ofTag(TagKey tag, int count) { return SizedIngredient.of(tag, count); } + @HideFromJS static SizedIngredient wrap(RegistryAccessContainer registries, Object from) { if (from instanceof SizedIngredient s) { return s; @@ -57,6 +59,7 @@ static SizedIngredient wrap(RegistryAccessContainer registries, Object from) { return IngredientJS.wrap(registries, from).kjs$asStack(); } + @HideFromJS static SizedIngredient read(RegistryAccessContainer registries, StringReader reader) throws CommandSyntaxException { int count = 1; diff --git a/src/main/java/dev/latvian/mods/kubejs/block/BlockRotationType.java b/src/main/java/dev/latvian/mods/kubejs/block/BlockRotationType.java new file mode 100644 index 000000000..c69b36bd1 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/block/BlockRotationType.java @@ -0,0 +1,9 @@ +package dev.latvian.mods.kubejs.block; + +public enum BlockRotationType { + NONE, + VERTICAL, + HORIZONTAL, + FACING, + WALL_ATTACHED +} diff --git a/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java b/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java index 2baea9f07..483287497 100644 --- a/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java @@ -11,7 +11,6 @@ import dev.latvian.mods.kubejs.util.Cast; import dev.latvian.mods.kubejs.util.ID; import dev.latvian.mods.kubejs.util.Lazy; -import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.rhino.NativeJavaMap; import dev.latvian.mods.rhino.type.TypeInfo; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; @@ -29,6 +28,9 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -253,23 +255,23 @@ static boolean filter(Object from, TypeInfo target) { return from == null || from instanceof DataComponentMap || from instanceof DataComponentPatch || from instanceof Map || from instanceof NativeJavaMap || from instanceof String s && (s.isEmpty() || s.charAt(0) == '['); } - static DataComponentMap mapOf(RegistryAccessContainer registries, Object o) { + static DataComponentMap mapOf(DynamicOps ops, Object o) { try { - return readMap(registries.nbt(), new StringReader(o.toString())); + return readMap(ops, new StringReader(o.toString())); } catch (CommandSyntaxException ex) { throw new RuntimeException("Error parsing DataComponentMap from " + o, ex); } } - static DataComponentPatch patchOf(RegistryAccessContainer registries, Object o) { + static DataComponentPatch patchOf(DynamicOps ops, Object o) { try { - return readPatch(registries.nbt(), new StringReader(o.toString())); + return readPatch(ops, new StringReader(o.toString())); } catch (CommandSyntaxException ex) { throw new RuntimeException("Error parsing DataComponentPatch from " + o, ex); } } - static StringBuilder mapToString(StringBuilder builder, DynamicOps dynamicOps, DataComponentMap map) { + static StringBuilder mapToString(StringBuilder builder, DynamicOps ops, DataComponentMap map) { builder.append('['); boolean first = true; @@ -282,7 +284,7 @@ static StringBuilder mapToString(StringBuilder builder, DynamicOps dynamicO } var id = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(comp.type()); - var optional = comp.encodeValue(dynamicOps).result(); + var optional = comp.encodeValue(ops).result(); if (id != null && !optional.isEmpty()) { builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(optional.get()); @@ -293,7 +295,7 @@ static StringBuilder mapToString(StringBuilder builder, DynamicOps dynamicO return builder; } - static StringBuilder patchToString(StringBuilder builder, DynamicOps dynamicOps, DataComponentPatch patch) { + static StringBuilder patchToString(StringBuilder builder, DynamicOps ops, DataComponentPatch patch) { builder.append('['); boolean first = true; @@ -309,7 +311,7 @@ static StringBuilder patchToString(StringBuilder builder, DynamicOps dynami if (id != null) { if (comp.getValue().isPresent()) { - var value = comp.getKey().codecOrThrow().encodeStart(dynamicOps, Cast.to(comp.getValue().get())).result().get(); + var value = comp.getKey().codecOrThrow().encodeStart(ops, Cast.to(comp.getValue().get())).result().get(); builder.append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()).append('=').append(value); } else { builder.append('!').append(id.getNamespace().equals("minecraft") ? id.getPath() : id.toString()); @@ -321,22 +323,20 @@ static StringBuilder patchToString(StringBuilder builder, DynamicOps dynami return builder; } - static void writeVisualComponentsForCache(StringBuilder builder, DynamicOps ops, DataComponentMap map) { - for (var entry : map) { - if (DataComponentWrapper.VISUAL_DIFFERENCE.get().contains(entry.type())) { - builder.append(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(entry.type())); - - if (entry.type().codec() != null) { - if (entry.value() instanceof CharSequence || entry.value() instanceof Number || entry.value() instanceof Boolean || entry.value() instanceof Tag) { - builder.append(entry.value()); - } else { - var str = entry.type().codec().encodeStart(ops, Cast.to(entry.value())).result().map(Object::toString).orElse(""); - builder.append(str.isEmpty() ? entry.value() : str); - } - } else { - builder.append(entry.value()); - } - } + static String urlEncodePatch(DynamicOps ops, DataComponentPatch patch) { + var sb = patchToString(new StringBuilder(), ops, patch); + return URLEncoder.encode(sb.substring(1, sb.length() - 1), StandardCharsets.UTF_8); + } + + static DataComponentPatch urlDecodePatch(DynamicOps ops, String s) { + if (s.isEmpty()) { + return DataComponentPatch.EMPTY; + } + + try { + return readPatch(ops, new StringReader("[" + URLDecoder.decode(s, StandardCharsets.UTF_8) + "]")); + } catch (Exception ex) { + return DataComponentPatch.EMPTY; } } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/BlockKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/BlockKJS.java index 94c49b15a..0529cbb92 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/BlockKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/BlockKJS.java @@ -60,4 +60,8 @@ public interface BlockKJS extends BlockBuilderProvider, RegistryObjectKJS default Object replaceThisWith(Context cx, Object with) { return with instanceof Block block ? block : with instanceof BlockState state ? state.getBlock() : cx.jsToJava(with, BlockWrapper.TYPE_INFO); } + + default String getWebIconURL(int size) { + return this instanceof Block block ? block.defaultBlockState().getWebIconURL(size) : ""; + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java index 41c8b2b46..3cef33cc4 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/BlockStateKJS.java @@ -5,6 +5,8 @@ import dev.latvian.mods.kubejs.block.RandomTickKubeEvent; import dev.latvian.mods.kubejs.recipe.match.Replaceable; import dev.latvian.mods.kubejs.script.ScriptType; +import dev.latvian.mods.kubejs.util.ID; +import dev.latvian.mods.kubejs.web.KubeJSLocalWebServer; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import net.minecraft.core.BlockPos; @@ -70,4 +72,11 @@ public interface BlockStateKJS extends RegistryObjectKJS, Replaceable { default Object replaceThisWith(Context cx, Object with) { return with instanceof BlockState state ? state : with instanceof Block block ? block.defaultBlockState() : cx.jsToJava(with, BlockWrapper.STATE_TYPE_INFO); } + + default String getWebIconURL(int size) { + var url = "/img/" + size + "/block/" + ID.url(kjs$getIdLocation()); + + + return KubeJSLocalWebServer.getURL(url); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java index 339a278ed..8b035c019 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/FluidStackKJS.java @@ -1,21 +1,100 @@ package dev.latvian.mods.kubejs.core; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; +import dev.latvian.mods.kubejs.component.DataComponentWrapper; import dev.latvian.mods.kubejs.component.MutableDataComponentHolderFunctions; import dev.latvian.mods.kubejs.fluid.FluidLike; import dev.latvian.mods.kubejs.fluid.FluidWrapper; import dev.latvian.mods.kubejs.recipe.match.FluidMatch; import dev.latvian.mods.kubejs.recipe.match.Replaceable; +import dev.latvian.mods.kubejs.util.ID; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; +import dev.latvian.mods.kubejs.util.WithCodec; +import dev.latvian.mods.kubejs.web.KubeJSLocalWebServer; import dev.latvian.mods.rhino.Context; +import dev.latvian.mods.rhino.util.SpecialEquality; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.material.Fluid; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.crafting.FluidIngredient; -public interface FluidStackKJS extends Replaceable, FluidLike, FluidMatch, MutableDataComponentHolderFunctions { +public interface FluidStackKJS extends + Replaceable, + SpecialEquality, + WithCodec, + FluidLike, + FluidMatch, + MutableDataComponentHolderFunctions, + RegistryObjectKJS { default FluidStack kjs$self() { return (FluidStack) (Object) this; } + @Override + default boolean specialEquals(Context cx, Object o, boolean shallow) { + if (o instanceof CharSequence) { + return kjs$getId().equals(ID.string(o.toString())); + } else if (o instanceof FluidStack s) { + return kjs$equalsIgnoringCount(s); + } + + return kjs$equalsIgnoringCount(FluidWrapper.wrap(RegistryAccessContainer.of(cx), o)); + } + + default boolean kjs$equalsIgnoringCount(FluidStack stack) { + var self = kjs$self(); + + if (self == stack) { + return true; + } else if (self.isEmpty()) { + return stack.isEmpty(); + } + + return FluidStack.isSameFluidSameComponents(self, stack); + } + + @Override + default ResourceKey> kjs$getRegistryId() { + return Registries.FLUID; + } + + @Override + default Registry kjs$getRegistry() { + return BuiltInRegistries.FLUID; + } + + @Override + default ResourceLocation kjs$getIdLocation() { + return kjs$self().getFluid().kjs$getIdLocation(); + } + + @Override + default Holder kjs$asHolder() { + return kjs$self().getFluid().kjs$asHolder(); + } + + @Override + default ResourceKey kjs$getKey() { + return kjs$self().getFluid().kjs$getKey(); + } + + @Override + default String kjs$getId() { + return kjs$self().getFluid().kjs$getId(); + } + + @Override + default String kjs$getMod() { + return kjs$self().getFluid().kjs$getMod(); + } + @Override default int kjs$getAmount() { return kjs$self().getAmount(); @@ -36,6 +115,11 @@ public interface FluidStackKJS extends Replaceable, FluidLike, FluidMatch, Mutab return (FluidLike) (Object) kjs$self().copyWithAmount(amount); } + @Override + default Codec getCodec(Context cx) { + return FluidStack.CODEC; + } + @Override default Object replaceThisWith(Context cx, Object with) { var t = kjs$self(); @@ -58,4 +142,14 @@ default boolean matches(Context cx, FluidStack s, boolean exact) { default boolean matches(Context cx, FluidIngredient ingredient, boolean exact) { return ingredient.test(kjs$self()); } + + default String getWebIconURL(DynamicOps ops, int size) { + var url = "/img/" + size + "/fluid/" + ID.url(kjs$getIdLocation()); + + if (!kjs$self().isComponentsPatchEmpty()) { + url += "?components=" + DataComponentWrapper.urlEncodePatch(ops, kjs$self().getComponentsPatch()); + } + + return KubeJSLocalWebServer.getURL(url); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java b/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java index 973a9964b..3961e2223 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/ItemStackKJS.java @@ -11,6 +11,7 @@ import dev.latvian.mods.kubejs.util.ID; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.kubejs.util.WithCodec; +import dev.latvian.mods.kubejs.web.KubeJSLocalWebServer; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import dev.latvian.mods.rhino.util.ReturnsSelf; @@ -109,6 +110,11 @@ default boolean specialEquals(Context cx, Object o, boolean shallow) { return kjs$self().getItem().kjs$getId(); } + @Override + default String kjs$getMod() { + return kjs$self().getItem().kjs$getMod(); + } + @Nullable default Block kjs$getBlock() { return kjs$self().getItem() instanceof BlockItem bi ? bi.getBlock() : null; @@ -166,11 +172,6 @@ default boolean specialEquals(Context cx, Object o, boolean shallow) { return is; } - @Override - default String kjs$getMod() { - return kjs$self().getItem().kjs$getMod(); - } - default boolean kjs$areItemsEqual(ItemStack other) { return kjs$self().getItem() == other.getItem(); } @@ -277,4 +278,14 @@ default boolean matches(Context cx, Ingredient in, boolean exact) { default boolean matches(Context cx, ItemLike itemLike, boolean exact) { return kjs$self().getItem() == itemLike.asItem(); } + + default String getWebIconURL(DynamicOps ops, int size) { + var url = "/img/" + size + "/item/" + ID.url(kjs$getIdLocation()); + + if (!kjs$self().isComponentsPatchEmpty()) { + url += "?components=" + DataComponentWrapper.urlEncodePatch(ops, kjs$self().getComponentsPatch()); + } + + return KubeJSLocalWebServer.getURL(url); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/core/mixin/ResourceKeyMixin.java b/src/main/java/dev/latvian/mods/kubejs/core/mixin/ResourceKeyMixin.java index 1eb7927d6..915e47e43 100644 --- a/src/main/java/dev/latvian/mods/kubejs/core/mixin/ResourceKeyMixin.java +++ b/src/main/java/dev/latvian/mods/kubejs/core/mixin/ResourceKeyMixin.java @@ -1,11 +1,8 @@ package dev.latvian.mods.kubejs.core.mixin; -import dev.latvian.mods.kubejs.KubeJS; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.util.RemapPrefixForJS; import dev.latvian.mods.rhino.util.SpecialEquality; -import net.minecraft.Util; -import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Final; @@ -35,6 +32,11 @@ public abstract class ResourceKeyMixin implements SpecialEquality { return location.getPath(); } + @Inject(method = "", at = @At(value = "RETURN")) + private void kjs$getKeyStackTraces(ResourceLocation registryName, ResourceLocation location, CallbackInfo ci) { + Scanner.scan(registryName, location); + } + @Override public boolean specialEquals(Context cx, Object o, boolean shallow) { if (this == o) { @@ -47,20 +49,4 @@ public boolean specialEquals(Context cx, Object o, boolean shallow) { return location.toString().equals(String.valueOf(o)); } } - - @Inject(method = "", at = @At(value = "RETURN")) - private void kjs$getKeyStackTraces(ResourceLocation registryName, ResourceLocation location, CallbackInfo ci){ - if (Scanner.isFrozen()) return; - if (!registryName.equals(Registries.ROOT_REGISTRY_NAME)) return; - if (Scanner.shouldSkipNamespace(location.getNamespace())) return; - var startTime = Util.getNanos(); - var stack = Thread.currentThread().getStackTrace(); - for (StackTraceElement stackTraceElement : stack) { - if (Scanner.shouldSkipModule(stackTraceElement.getModuleName())) continue; - var className = stackTraceElement.getClassName(); - if (Scanner.contains(className)) continue; - Scanner.add(className); - } - KubeJS.LOGGER.debug("Took {} ms to grab stacktrace classes.", (int)((Util.getNanos() - startTime)/1_000_000)); - } } diff --git a/src/main/java/dev/latvian/mods/kubejs/fluid/FluidWrapper.java b/src/main/java/dev/latvian/mods/kubejs/fluid/FluidWrapper.java index 4f6586a36..cb41687e1 100644 --- a/src/main/java/dev/latvian/mods/kubejs/fluid/FluidWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/fluid/FluidWrapper.java @@ -7,6 +7,7 @@ import dev.latvian.mods.kubejs.util.RegExpKJS; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.rhino.type.TypeInfo; +import dev.latvian.mods.rhino.util.HideFromJS; import net.minecraft.core.HolderSet; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPredicate; @@ -35,6 +36,7 @@ public interface FluidWrapper { SizedFluidIngredient EMPTY_SIZED = new SizedFluidIngredient(FluidIngredient.empty(), FluidType.BUCKET_VOLUME); + @HideFromJS static FluidStack wrap(RegistryAccessContainer registries, Object o) { if (o == null || o == FluidStack.EMPTY || o == Fluids.EMPTY || o == EmptyFluidIngredient.INSTANCE) { return FluidStack.EMPTY; @@ -51,6 +53,11 @@ static FluidStack wrap(RegistryAccessContainer registries, Object o) { } } + static FluidIngredient ingredientOf(FluidIngredient of) { + return of; + } + + @HideFromJS static FluidIngredient wrapIngredient(RegistryAccessContainer registries, Object o) { if (o == null || o == FluidStack.EMPTY || o == Fluids.EMPTY || o == EmptyFluidIngredient.INSTANCE) { return EmptyFluidIngredient.INSTANCE; @@ -67,6 +74,11 @@ static FluidIngredient wrapIngredient(RegistryAccessContainer registries, Object } } + static SizedFluidIngredient sizedIngredientOf(SizedFluidIngredient of) { + return of; + } + + @HideFromJS static SizedFluidIngredient wrapSizedIngredient(RegistryAccessContainer registries, Object o) { if (o == null || o == FluidStack.EMPTY || o == Fluids.EMPTY || o == EmptyFluidIngredient.INSTANCE) { return EMPTY_SIZED; diff --git a/src/main/java/dev/latvian/mods/kubejs/registry/RegistryType.java b/src/main/java/dev/latvian/mods/kubejs/registry/RegistryType.java index 208dc469a..a66f62896 100644 --- a/src/main/java/dev/latvian/mods/kubejs/registry/RegistryType.java +++ b/src/main/java/dev/latvian/mods/kubejs/registry/RegistryType.java @@ -9,7 +9,7 @@ import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; - +import net.minecraft.resources.ResourceLocation; import net.neoforged.neoforge.common.util.Lazy; import net.neoforged.neoforge.registries.NeoForgeRegistries; import org.jetbrains.annotations.Nullable; @@ -29,11 +29,6 @@ public record RegistryType(ResourceKey> key, Class baseClass, private static final Map> TYPE_MAP = new HashMap<>(); private static final Map, List>> CLASS_MAP = new Reference2ObjectOpenHashMap<>(); - // This is cursed, but it's better than manually registering every type - public static synchronized void init() { - Scanner.processClass(Stream.of(Registries.class, NeoForgeRegistries.Keys.class)); - } - public static synchronized void register(ResourceKey> key, TypeInfo type) { var t = new RegistryType<>(key, type.asClass(), type); KEY_MAP.put(key, t); @@ -65,7 +60,7 @@ public static synchronized List> allOfClass(Class type) { return (List>) of(type); } - private static synchronized Object of(Object obj){ + private static synchronized Object of(Object obj) { Scanner.startIfNotFrozen(); return switch (obj) { case ResourceKey key -> KEY_MAP.get(key); @@ -103,64 +98,60 @@ public static class Scanner { set.add(ResourceKey.class); set.add(Registry.class); var registrar = UtilsJS.tryLoadClass("dev.architectury.registry.registries.Registrar"); - if (registrar != null) set.add(registrar); + if (registrar != null) { + set.add(registrar); + } return set; }); + private static final Set CLASSES_TO_SCAN = new HashSet<>(); - private static final Set MODULES_TO_SKIP = Set.of("java.base","neoforge","fml_loader","kubejs"); + private static final Set MODULES_TO_SKIP = Set.of("java.base", "neoforge", "fml_loader", "kubejs"); private static final Set NAMESPACES_TO_SKIP = Set.of("neoforge", "minecraft"); private static boolean frozen = false; - private static synchronized void startIfNotFrozen(){ - if (isFrozen()) return; - frozen = true; - var startTime = Util.getNanos(); - Stream> classStream = CLASSES_TO_SCAN.stream().map(UtilsJS::tryLoadClass); - processClass(classStream); - CLASSES_TO_SCAN.clear(); - KubeJS.LOGGER.debug("Took {} ms to discover registry classes.", (int)((Util.getNanos() - startTime)/1_000_000)); - } - - public static synchronized boolean isFrozen(){ - return frozen; - } - - public static synchronized boolean shouldSkipModule(String moduleName){ - return MODULES_TO_SKIP.contains(moduleName); - } - - public static synchronized boolean shouldSkipNamespace(String namespace){ - return NAMESPACES_TO_SKIP.contains(namespace); + public static synchronized void init() { + processClass(Stream.of(Registries.class, NeoForgeRegistries.Keys.class)); } - public static synchronized void add(String className){ - CLASSES_TO_SCAN.add(className); - } + private static synchronized void startIfNotFrozen() { + if (frozen) { + return; + } - public static synchronized boolean contains(String className){ - return CLASSES_TO_SCAN.contains(className); + frozen = true; + var startTime = Util.getNanos(); + processClass(CLASSES_TO_SCAN.stream().map(UtilsJS::tryLoadClass)); + CLASSES_TO_SCAN.clear(); + KubeJS.LOGGER.debug("Took {} ms to discover registry classes.", (int) ((Util.getNanos() - startTime) / 1_000_000)); } - private static synchronized void processClass(Stream> classStream){ + private static void processClass(Stream> classStream) { classStream.map(Class::getDeclaredFields) .flatMap(Stream::of) .forEach(field -> { try { - if (!VALID_TYPES.get().contains(field.getType())) return; - if (!Modifier.isPublic(field.getModifiers())) field.setAccessible(true); + if (!VALID_TYPES.get().contains(field.getType())) { + return; + } + + if (!Modifier.isPublic(field.getModifiers())) { + field.setAccessible(true); + } + var value = field.get(null); + if (value instanceof ResourceKey key) { if (field.getGenericType() instanceof ParameterizedType t1 && t1.getActualTypeArguments()[0] instanceof ParameterizedType t2) { processKey(key, t2, false); } } else if (value instanceof Registry registry) { - if (field.getGenericType() instanceof ParameterizedType t1){ + if (field.getGenericType() instanceof ParameterizedType t1) { processKey(registry.key(), t1, true); } - } else if (field.getType().getName().equals("dev.architectury.registry.registries.Registrar")){ - if (field.getGenericType() instanceof ParameterizedType t1){ + } else if (field.getType().getName().equals("dev.architectury.registry.registries.Registrar")) { + if (field.getGenericType() instanceof ParameterizedType t1) { var method = value.getClass().getDeclaredMethod("key"); processKey((ResourceKey) method.invoke(value), t1, true); } @@ -171,11 +162,46 @@ private static synchronized void processClass(Stream> classStream){ }); } - private static synchronized void processKey(ResourceKey key, ParameterizedType paramType, boolean checkIfContains){ - if (checkIfContains && RegistryType.ofKey(key) != null) return; + private static void processKey(ResourceKey key, ParameterizedType paramType, boolean checkIfContains) { + if (checkIfContains && RegistryType.ofKey(key) != null) { + return; + } var type = paramType.getActualTypeArguments()[0]; var typeInfo = TypeInfo.of(type); register(key, typeInfo); } + + public static synchronized void scan(ResourceLocation registryName, ResourceLocation location) { + if (frozen) { + return; + } + + if (!registryName.equals(Registries.ROOT_REGISTRY_NAME)) { + return; + } + + if (NAMESPACES_TO_SKIP.contains(location.getNamespace())) { + return; + } + + var startTime = Util.getNanos(); + var stack = Thread.currentThread().getStackTrace(); + + for (var stackTraceElement : stack) { + if (MODULES_TO_SKIP.contains(stackTraceElement.getModuleName())) { + continue; + } + + var className = stackTraceElement.getClassName(); + + if (CLASSES_TO_SCAN.contains(className)) { + continue; + } + + CLASSES_TO_SCAN.add(className); + } + + KubeJS.LOGGER.debug("Took {} ms to grab stacktrace classes.", (int) ((Util.getNanos() - startTime) / 1_000_000)); + } } } diff --git a/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java b/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java index cbede3449..b0c658df2 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java @@ -46,6 +46,15 @@ public void unload() { public void reload() { KubeJSPlugins.forEachPlugin(KubeJSPlugin::clearCaches); + long start = System.currentTimeMillis(); + + KubeJSWeb.broadcastUpdate("before_scripts_loaded", () -> { + var broadcast = new JsonObject(); + broadcast.addProperty("type", scriptType.name); + broadcast.addProperty("time", start); + return broadcast; + }); + unload(); scriptType.console.writeToFile(LogType.INIT, "KubeJS " + KubeJS.VERSION + "; MC " + KubeJS.MC_VERSION_NUMBER + " NeoForge"); scriptType.console.writeToFile(LogType.INIT, "Loaded plugins:"); @@ -56,7 +65,7 @@ public void reload() { KubeJSPlugins.forEachPlugin(this, KubeJSPlugin::beforeScriptsLoaded); loadFromDirectory(); - load(); + load(start); KubeJSPlugins.forEachPlugin(this, KubeJSPlugin::afterScriptsLoaded); } @@ -139,9 +148,7 @@ public boolean isClassAllowed(String name) { return classFilter.isAllowed(name); } - private void load() { - var startAll = System.currentTimeMillis(); - + private void load(long startAll) { contextFactory = new KubeJSContextFactory(this); scriptType.console.contextFactory = new WeakReference<>(contextFactory); @@ -187,7 +194,8 @@ private void load() { } loadAdditional(); - long ms = System.currentTimeMillis() - startAll; + long end = System.currentTimeMillis(); + long ms = end - startAll; scriptType.console.info("Loaded " + i + "/" + t + " KubeJS " + scriptType.name + " scripts in " + ms / 1000D + " s with " + scriptType.console.errors.size() + " errors and " + scriptType.console.warnings.size() + " warnings"); canListenEvents = false; @@ -200,14 +208,15 @@ private void load() { int t1 = t; int i1 = i; - KubeJSWeb.broadcastUpdate("scripts_reloaded", () -> { + KubeJSWeb.broadcastUpdate("after_scripts_loaded", () -> { var broadcast = new JsonObject(); broadcast.addProperty("type", scriptType.name); broadcast.addProperty("total", t1); broadcast.addProperty("successful", i1); broadcast.addProperty("errors", scriptType.console.errors.size()); broadcast.addProperty("warnings", scriptType.console.warnings.size()); - broadcast.addProperty("time", ms); + broadcast.addProperty("time", end); + broadcast.addProperty("duration", ms); return broadcast; }); } diff --git a/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java b/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java new file mode 100644 index 000000000..6ce32c8ac --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java @@ -0,0 +1,35 @@ +package dev.latvian.mods.kubejs.util; + +import dev.latvian.mods.kubejs.core.RegistryObjectKJS; +import io.netty.buffer.Unpooled; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.FriendlyByteBuf; + +import java.util.UUID; + +public record CachedComponentObject>(UUID cacheKey, T value, DataComponentPatch components) { + public static > CachedComponentObject of(T value, DataComponentPatch components) { + var buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUtf(value.kjs$getId()); + buf.writeVarInt(components.size()); + + for (var entry : components.entrySet()) { + var key = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(entry.getKey()); + buf.writeUtf(key.getNamespace()); + buf.writeUtf(key.getPath()); + buf.writeBoolean(entry.getValue().isPresent()); + + if (entry.getValue().isPresent()) { + buf.writeVarInt(entry.getValue().get().hashCode()); + } + } + + return new CachedComponentObject<>(UUID.nameUUIDFromBytes(buf.array()), value, components); + } + + @Override + public boolean equals(Object o) { + return this == o || o instanceof CachedComponentObject c && value == c.value && components.equals(c.components); + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/util/UtilsJS.java b/src/main/java/dev/latvian/mods/kubejs/util/UtilsJS.java index 8ea4c2178..e87b63dc1 100644 --- a/src/main/java/dev/latvian/mods/kubejs/util/UtilsJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/util/UtilsJS.java @@ -411,11 +411,11 @@ public static T makeFunctionProxy(Context cx, TypeInfo targetClass, BaseFunc @Nullable @HideFromJS - public static Class tryLoadClass(String className){ - Class clazz = null; + public static Class tryLoadClass(String className) { try { - clazz = Class.forName(className); - } catch (Exception ignored) {} - return clazz; + return Class.forName(className); + } catch (Exception ignored) { + return null; + } } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java b/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java index ee3a05398..c186a6ef3 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java @@ -1,16 +1,13 @@ package dev.latvian.mods.kubejs.web; -import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.DynamicOps; import dev.latvian.apps.tinyserver.http.HTTPRequest; -import dev.latvian.mods.kubejs.util.Cast; +import dev.latvian.mods.kubejs.component.DataComponentWrapper; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import net.minecraft.client.Minecraft; import net.minecraft.core.component.DataComponentPatch; -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.Tag; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceLocation; import java.util.concurrent.CompletableFuture; @@ -37,25 +34,7 @@ public ResourceLocation id() { return id("namespace", "path"); } - public DataComponentPatch queryAsPatch(DynamicOps ops) throws CommandSyntaxException { - if (query().isEmpty()) { - return DataComponentPatch.EMPTY; - } - - var builder = DataComponentPatch.builder(); - - for (var entry : query().entrySet()) { - var dataComponentType = BuiltInRegistries.DATA_COMPONENT_TYPE.get(ResourceLocation.parse(entry.getKey())); - - if (dataComponentType != null && !dataComponentType.isTransient()) { - var dataResult = dataComponentType.codecOrThrow().parse(ops, new TagParser(new StringReader(entry.getValue())).readValue()); - - if (dataResult.isSuccess() && dataResult.result().isPresent()) { - builder.set(dataComponentType, Cast.to(dataResult.result().get())); - } - } - } - - return builder.build(); + public DataComponentPatch components(DynamicOps ops) throws CommandSyntaxException { + return DataComponentWrapper.urlDecodePatch(ops, query().getOrDefault("components", "")); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java b/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java index 12e21a72b..c35159466 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KJSWSSession.java @@ -11,12 +11,12 @@ public void onTextMessage(String message) { if (message.startsWith("{") && message.endsWith("}")) { var json = JsonUtils.fromString(message).getAsJsonObject(); - if (json.has("event")) { - onEvent(json.get("event").getAsString(), json.has("payload") ? json.get("payload") : JsonNull.INSTANCE); + if (json.has("type")) { + onEvent(json.get("type").getAsString(), json.has("payload") ? json.get("payload") : JsonNull.INSTANCE); } } } - public void onEvent(String event, JsonElement payload) { + public void onEvent(String type, JsonElement payload) { } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java b/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java index 7d158997e..899462ab6 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java @@ -18,7 +18,7 @@ import java.util.ArrayList; -public record KubeJSLocalWebServer(HTTPServer server) implements ServerRegistry { +public record KubeJSLocalWebServer(HTTPServer server, String url) implements ServerRegistry { private static KubeJSLocalWebServer instance; @Nullable @@ -26,14 +26,17 @@ public static KubeJSLocalWebServer instance() { return instance; } + public static String getURL(String path) { + return instance == null ? "" : instance.url + path; + } + @HideFromJS public static void start() { if (instance == null) { try { var server = new HTTPServer<>(KJSHTTPRequest::new); // var ws = new WSServer(address, ClientProperties.get().localServerWsPort); - var s = new KubeJSLocalWebServer(server); - KubeJSPlugins.forEachPlugin(s, KubeJSPlugin::registerLocalWebServer); + KubeJSPlugins.forEachPlugin(server, KubeJSPlugin::registerLocalWebServer); server.get("/", KubeJSLocalWebServer::homepage); server.setDaemon(true); @@ -42,12 +45,13 @@ public static void start() { server.setPort(WebServerProperties.get().port); server.setMaxPortShift(10); - KubeJS.LOGGER.info("Started the local web server at http://localhost:" + server.start()); - instance = s; + var url = "http://localhost:" + server.start(); + KubeJS.LOGGER.info("Started the local web server at " + url); + instance = new KubeJSLocalWebServer(server, url); } catch (BindFailedException ex) { KubeJS.LOGGER.warn("Failed to start the local web server - all ports occupied"); } catch (Exception ex) { - KubeJS.LOGGER.warn("Failed to start the local web server - error"); + KubeJS.LOGGER.warn("Failed to start the local web server - unexpected error"); ex.printStackTrace(); } } @@ -63,7 +67,7 @@ public > WSHandler ws return server.ws(s, wsSessionFactory); } - private static HTTPResponse homepage(KJSHTTPRequest ctx) { + private static HTTPResponse homepage(KJSHTTPRequest req) { var list = new ArrayList(); list.add("KubeJS Local Web Server [" + KubeJS.PROXY.getWebServerWindowTitle() + "]"); list.add(""); @@ -83,9 +87,4 @@ private static HTTPResponse homepage(KJSHTTPRequest ctx) { return HTTPResponse.ok().text(list); } - - public void stopNow() { - server.stop(); - KubeJS.LOGGER.info("Stopped the local web server"); - } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java b/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java index ed50c34b7..3d04ebbc3 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java @@ -12,8 +12,8 @@ public ConsoleWSSession(ConsoleJS console) { } @Override - public void onEvent(String event, JsonElement payload) { - switch (event) { + public void onEvent(String type, JsonElement payload) { + switch (type) { case "info" -> console.info(payload.getAsString()); case "warn" -> console.warn(payload.getAsString()); case "error" -> console.error(payload.getAsString()); diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java index f12d45558..335d9c0d4 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java @@ -26,7 +26,7 @@ public class KubeJSWeb { public static void broadcastEvent(WSHandler ws, String event, Supplier payload) { ws.broadcastText(() -> { var json = new JsonObject(); - json.addProperty("event", event); + json.addProperty("type", event); var p = payload == null ? null : payload.get(); @@ -38,12 +38,12 @@ public static void broadcastEvent(WSHandler ws, St }); } - public static void broadcastUpdate(String event, Supplier payload) { - broadcastEvent(UPDATES, event, payload); + public static void broadcastUpdate(String type, Supplier payload) { + broadcastEvent(UPDATES, type, payload); } - public static void addScriptTypeEndpoints(ServerRegistry registry, ScriptType s) { - var path = "api/console/" + s.name; + public static void addScriptTypeEndpoints(ServerRegistry registry, ScriptType s, Runnable reload) { + var path = "/api/console/" + s.name; s.console.wsBroadcaster = registry.ws(path + "/stream", () -> new ConsoleWSSession(s.console)); @@ -52,24 +52,23 @@ public static void addScriptTypeEndpoints(ServerRegistry registr registry.acceptPostString(path + "/error", s.console::error); registry.get(path + "/errors", s.console::getErrorsResponse); registry.get(path + "/warnings", s.console::getWarningsResponse); + + registry.acceptPostTask("/api/reload/" + s.name, reload); } public static void register(ServerRegistry registry) { - UPDATES = registry.ws("updates", KJSWSSession::new); - - addScriptTypeEndpoints(registry, ScriptType.STARTUP); - addScriptTypeEndpoints(registry, ScriptType.SERVER); + UPDATES = registry.ws("/api/updates", KJSWSSession::new); - registry.acceptPostTask("api/reload/startup", KubeJS.getStartupScriptManager()::reload); - registry.acceptPostTask("api/reload/server", KubeJSWeb::reloadInternalServer); + addScriptTypeEndpoints(registry, ScriptType.STARTUP, KubeJS.getStartupScriptManager()::reload); + addScriptTypeEndpoints(registry, ScriptType.SERVER, KubeJSWeb::reloadInternalServer); - registry.get("api/registries", KubeJSWeb::getRegistriesResponse); // List of all registries - registry.get("api/registries/{namespace}/{path}/keys", KubeJSWeb::getRegistryKeysResponse); // List of all IDs in registry - registry.get("api/registries/{namespace}/{path}/match/{regex}", KubeJSWeb::getRegistryMatchResponse); // List of RegEx matched IDs in registry + registry.get("/api/registries", KubeJSWeb::getRegistriesResponse); // List of all registries + registry.get("/api/registries/{namespace}/{path}/keys", KubeJSWeb::getRegistryKeysResponse); // List of all IDs in registry + registry.get("/api/registries/{namespace}/{path}/match/{regex}", KubeJSWeb::getRegistryMatchResponse); // List of RegEx matched IDs in registry - registry.get("api/tags/{namespace}/{path}", KubeJSWeb::getTagsResponse); // List of all tags in registry - registry.get("api/tags/{namespace}/{path}/values/{tag-namespace}/{tag-path}", KubeJSWeb::getTagValuesResponse); // List of all values in a tag - registry.get("api/tags/{namespace}/{path}/keys/{value-namespace}/{value-path}", KubeJSWeb::getTagKeysResponse); // List of all tags for a value + registry.get("/api/tags/{namespace}/{path}", KubeJSWeb::getTagsResponse); // List of all tags in registry + registry.get("/api/tags/{namespace}/{path}/values/{tag-namespace}/{tag-path}", KubeJSWeb::getTagValuesResponse); // List of all values in a tag + registry.get("/api/tags/{namespace}/{path}/keys/{value-namespace}/{value-path}", KubeJSWeb::getTagKeysResponse); // List of all tags for a value } private static void reloadInternalServer() { @@ -80,16 +79,16 @@ private static void reloadInternalServer() { } } - private static HTTPResponse getRegistriesResponse(KJSHTTPRequest ctx) { + private static HTTPResponse getRegistriesResponse(KJSHTTPRequest req) { return HTTPResponse.ok().content(JsonContent.array(json -> { - for (var registry : ctx.registries().access().registries().toList()) { + for (var registry : req.registries().access().registries().toList()) { json.add(registry.key().location().toString()); } })); } - private static HTTPResponse getRegistryKeysResponse(KJSHTTPRequest ctx) { - var registry = ctx.registries().access().registry(ResourceKey.createRegistryKey(ctx.id())); + private static HTTPResponse getRegistryKeysResponse(KJSHTTPRequest req) { + var registry = req.registries().access().registry(ResourceKey.createRegistryKey(req.id())); if (registry.isEmpty()) { return HTTPStatus.NOT_FOUND; @@ -102,14 +101,14 @@ private static HTTPResponse getRegistryKeysResponse(KJSHTTPRequest ctx) { })); } - private static HTTPResponse getRegistryMatchResponse(KJSHTTPRequest ctx) { - var registry = ctx.registries().access().registry(ResourceKey.createRegistryKey(ctx.id())); + private static HTTPResponse getRegistryMatchResponse(KJSHTTPRequest req) { + var registry = req.registries().access().registry(ResourceKey.createRegistryKey(req.id())); if (registry.isEmpty()) { return HTTPStatus.NOT_FOUND; } - var regex = RegExpKJS.ofString(ctx.variables().get("regex")); + var regex = RegExpKJS.ofString(req.variables().get("regex")); if (regex == null) { return HTTPStatus.BAD_REQUEST; @@ -126,8 +125,8 @@ private static HTTPResponse getRegistryMatchResponse(KJSHTTPRequest ctx) { })); } - private static HTTPResponse getTagsResponse(KJSHTTPRequest ctx) { - var registry = ctx.registries().access().registry(ResourceKey.createRegistryKey(ctx.id())); + private static HTTPResponse getTagsResponse(KJSHTTPRequest req) { + var registry = req.registries().access().registry(ResourceKey.createRegistryKey(req.id())); if (registry.isEmpty()) { return HTTPStatus.NOT_FOUND; @@ -140,14 +139,14 @@ private static HTTPResponse getTagsResponse(KJSHTTPRequest ctx) { })); } - private static HTTPResponse getTagValuesResponse(KJSHTTPRequest ctx) { - var registry = ctx.registries().access().registry(ResourceKey.createRegistryKey(ctx.id())); + private static HTTPResponse getTagValuesResponse(KJSHTTPRequest req) { + var registry = req.registries().access().registry(ResourceKey.createRegistryKey(req.id())); if (registry.isEmpty()) { return HTTPStatus.NOT_FOUND; } - var tagKey = registry.get().getTag(TagKey.create(registry.get().key(), ctx.id("tag-namespace", "tag-path"))); + var tagKey = registry.get().getTag(TagKey.create(registry.get().key(), req.id("tag-namespace", "tag-path"))); if (tagKey.isEmpty()) { return HTTPStatus.NOT_FOUND; @@ -160,14 +159,14 @@ private static HTTPResponse getTagValuesResponse(KJSHTTPRequest ctx) { })); } - private static HTTPResponse getTagKeysResponse(KJSHTTPRequest ctx) { - var registry = ctx.registries().access().registry(ResourceKey.createRegistryKey(ctx.id())); + private static HTTPResponse getTagKeysResponse(KJSHTTPRequest req) { + var registry = req.registries().access().registry(ResourceKey.createRegistryKey(req.id())); if (registry.isEmpty()) { return HTTPStatus.NOT_FOUND; } - var value = registry.get().getHolder(ctx.id("value-namespace", "value-path")); + var value = registry.get().getHolder(req.id("value-namespace", "value-path")); if (value.isEmpty()) { return HTTPStatus.NOT_FOUND; diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/client/FakeClientWorld.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/FakeClientWorld.java new file mode 100644 index 000000000..f346b4255 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/FakeClientWorld.java @@ -0,0 +1,70 @@ +package dev.latvian.mods.kubejs.web.local.client; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.ColorResolver; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import org.jetbrains.annotations.Nullable; + +public class FakeClientWorld implements BlockAndTintGetter { + public final LevelReader parent; + public final BlockState blockState; + public final Biome biome; + + public FakeClientWorld(LevelReader parent, BlockState blockState, ResourceKey biome) { + this.parent = parent; + this.blockState = blockState; + this.biome = parent.registryAccess().registryOrThrow(Registries.BIOME).get(biome); + } + + @Override + public float getShade(Direction direction, boolean shade) { + return parent.getShade(direction, shade); + } + + @Override + public LevelLightEngine getLightEngine() { + return parent.getLightEngine(); + } + + @Override + public int getBlockTint(BlockPos pos, ColorResolver colorResolver) { + return colorResolver.getColor(biome, 0D, 0D); + } + + @Override + @Nullable + public BlockEntity getBlockEntity(BlockPos pos) { + return null; + } + + @Override + public BlockState getBlockState(BlockPos pos) { + return pos.equals(BlockPos.ZERO) ? blockState : Blocks.AIR.defaultBlockState(); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return pos.equals(BlockPos.ZERO) ? blockState.getFluidState() : Fluids.EMPTY.defaultFluidState(); + } + + @Override + public int getHeight() { + return 1; + } + + @Override + public int getMinBuildHeight() { + return 0; + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java index 98bc5f685..3f9ae2099 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java @@ -1,6 +1,7 @@ package dev.latvian.mods.kubejs.web.local.client; import com.mojang.blaze3d.pipeline.TextureTarget; +import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.BufferUploader; @@ -13,23 +14,33 @@ import dev.latvian.mods.kubejs.KubeJSPaths; import dev.latvian.mods.kubejs.bindings.BlockWrapper; import dev.latvian.mods.kubejs.bindings.UUIDWrapper; -import dev.latvian.mods.kubejs.component.DataComponentWrapper; +import dev.latvian.mods.kubejs.util.CachedComponentObject; import dev.latvian.mods.kubejs.web.KJSHTTPRequest; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.ItemBlockRenderTypes; +import net.minecraft.client.renderer.block.model.ItemTransform; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.tags.BlockTags; import net.minecraft.tags.FluidTags; import net.minecraft.tags.ItemTags; +import net.minecraft.util.RandomSource; import net.minecraft.world.item.Items; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.material.Fluids; import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; +import net.neoforged.neoforge.client.model.data.ModelData; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.FluidType; +import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; +import org.joml.Vector3f; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -37,38 +48,36 @@ import java.util.function.Consumer; public class ImageGenerator { + public static final ItemTransform ROTATED_BLOCK_TRANSFORM = new ItemTransform( + new Vector3f(30F, 225F, 0F), + new Vector3f(0F, 0F, 0F), + new Vector3f(0.625F, 0.625F, 0.625F) + ); + private record RenderImage(Minecraft mc, GuiGraphics graphics, int size) { } public static final Int2ObjectMap FB_CACHE = new Int2ObjectArrayMap<>(); - private static HTTPResponse renderCanvas(KJSHTTPRequest ctx, int canvasSize, StringBuilder cacheId, Consumer render) { - int size = Integer.parseInt(ctx.variables().get("size")); + private static HTTPResponse renderCanvas(KJSHTTPRequest req, int canvasSize, String dir, StringBuilder cacheId, Consumer render) { + return renderCanvas(req, canvasSize, dir, cacheId.isEmpty() ? null : UUID.nameUUIDFromBytes(cacheId.toString().getBytes(StandardCharsets.UTF_8)), render); + } + + private static HTTPResponse renderCanvas(KJSHTTPRequest req, int canvasSize, String dir, @Nullable UUID cacheUUID, Consumer render) { + int size = Integer.parseInt(req.variables().get("size")); if (size < 1 || size > 1024) { return HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"); } - if (!cacheId.isEmpty()) { - cacheId.append(size); - } - - if (cacheId.isEmpty() && ctx.header("Accept").equals("text/plain")) { - return HTTPStatus.NOT_FOUND; - } - - var cacheUUID = cacheId.isEmpty() ? null : UUIDWrapper.toString(UUID.nameUUIDFromBytes(cacheId.toString().getBytes(StandardCharsets.UTF_8))); - var cachePath = cacheUUID == null ? null : KubeJSPaths.dir(KubeJSPaths.LOCAL_WEB_IMG_CACHE.resolve(cacheUUID.substring(0, 2))).resolve(cacheUUID + ".png"); + var cacheUUIDStr = cacheUUID == null || req.query().containsKey("uncached") ? null : UUIDWrapper.toString(cacheUUID); + var cachePath = cacheUUIDStr == null ? null : KubeJSPaths.dir(KubeJSPaths.LOCAL.resolve("cache/web/img/" + dir + "/" + cacheUUIDStr.substring(0, 2))).resolve(cacheUUIDStr + "_" + size + ".png"); if (cachePath != null && Files.exists(cachePath)) { - if (ctx.header("Accept").equals("text/plain")) { - return HTTPResponse.ok().text(cacheUUID); - } - - return HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUID); + return HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUIDStr); } - var bytes = ctx.supplyInRenderThread(() -> { + var bytes = req.supplyInRenderThread(() -> { var target = FB_CACHE.get(size); if (target == null) { @@ -119,37 +128,33 @@ private static HTTPResponse renderCanvas(KJSHTTPRequest ctx, int canvasSize, Str } } - if (ctx.header("Accept").equals("text/plain")) { + if (req.header("Accept").equals("text/plain")) { if (cachePath == null) { return HTTPStatus.NOT_FOUND; } - return HTTPResponse.ok().text(cacheUUID); + return HTTPResponse.ok().text(cacheUUIDStr); } - return HTTPResponse.ok().content(bytes, "image/png").header("X-KubeJS-Cache-Key", cacheUUID); + return HTTPResponse.ok().content(bytes, "image/png").header("X-KubeJS-Cache-Key", cacheUUIDStr); } - public static HTTPResponse item(KJSHTTPRequest ctx) throws Exception { - var stack = BuiltInRegistries.ITEM.get(ctx.id()).getDefaultInstance(); - stack.applyComponents(ctx.queryAsPatch(ctx.registries().nbt())); + public static HTTPResponse item(KJSHTTPRequest req) throws Exception { + var stack = BuiltInRegistries.ITEM.get(req.id()).getDefaultInstance(); + stack.applyComponents(req.components(req.registries().nbt())); if (stack.isEmpty()) { return HTTPStatus.NOT_FOUND; } - var sb = new StringBuilder(); - sb.append(stack.kjs$getId()); - DataComponentWrapper.writeVisualComponentsForCache(sb, ctx.registries().nbt(), stack.getComponents()); - - return renderCanvas(ctx, 16, sb, render -> { + return renderCanvas(req, 16, "item", CachedComponentObject.of(stack.getItem(), stack.getComponentsPatch()).cacheKey(), render -> { render.graphics.renderFakeItem(stack, 0, 0, 0); render.graphics.renderItemDecorations(render.mc.font, stack, 0, 0); }); } - public static HTTPResponse block(KJSHTTPRequest ctx) throws Exception { - var state = BlockWrapper.withProperties(BuiltInRegistries.BLOCK.get(ctx.id()).defaultBlockState(), ctx.query()); + public static HTTPResponse block(KJSHTTPRequest req) throws Exception { + var state = BlockWrapper.withProperties(BuiltInRegistries.BLOCK.get(req.id()).defaultBlockState(), req.query()); if (state.isEmpty()) { return HTTPStatus.NOT_FOUND; @@ -158,19 +163,55 @@ public static HTTPResponse block(KJSHTTPRequest ctx) throws Exception { var sb = new StringBuilder(); sb.append(state.kjs$getId()); - for (var entry : ctx.query().entrySet()) { + for (var entry : req.query().entrySet()) { sb.append(entry.getKey()); sb.append(entry.getValue()); } - return renderCanvas(ctx, 16, sb, render -> { - render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); + return renderCanvas(req, 16, "block", sb, render -> { + var model = render.mc.getBlockRenderer().getBlockModel(state); + var pose = render.graphics.pose(); + pose.pushPose(); + pose.translate(8F, 8F, 150F); + pose.scale(16F, -16F, 16F); + + boolean flag = !model.usesBlockLight(); + + if (flag) { + Lighting.setupForFlatItems(); + } + + // model = ClientHooks.handleCameraTransforms(pose, model, ItemDisplayContext.GUI, false); + ROTATED_BLOCK_TRANSFORM.apply(false, pose); + pose.translate(-0.5F, -0.5F, -0.5F); + + for (var renderType : model.getRenderTypes(state, RandomSource.create(0L), ModelData.EMPTY)) { + render.mc.getBlockRenderer().renderSingleBlock(state, pose, render.graphics.bufferSource(), 15728880, OverlayTexture.NO_OVERLAY, ModelData.EMPTY, renderType); + } + + try { + var fluidState = state.getFluidState(); + + if (!fluidState.is(Fluids.EMPTY)) { + var world = new FakeClientWorld(render.mc.level, state, Biomes.THE_VOID); + render.mc.getBlockRenderer().renderLiquid(BlockPos.ZERO, world, new MovedVertexConsumer(render.graphics.bufferSource().getBuffer(ItemBlockRenderTypes.getRenderLayer(fluidState)), pose.last()), state, fluidState); + } + } catch (Exception ignored) { + } + + render.graphics.flush(); + + if (flag) { + Lighting.setupFor3DItems(); + } + + render.graphics.pose().popPose(); }); } - public static HTTPResponse fluid(KJSHTTPRequest ctx) throws Exception { - var stack = new FluidStack(BuiltInRegistries.FLUID.get(ctx.id()), FluidType.BUCKET_VOLUME); - stack.applyComponents(ctx.queryAsPatch(ctx.registries().nbt())); + public static HTTPResponse fluid(KJSHTTPRequest req) throws Exception { + var stack = new FluidStack(BuiltInRegistries.FLUID.get(req.id()), FluidType.BUCKET_VOLUME); + stack.applyComponents(req.components(req.registries().nbt())); if (stack.isEmpty()) { return HTTPStatus.NOT_FOUND; @@ -184,11 +225,7 @@ public static HTTPResponse fluid(KJSHTTPRequest ctx) throws Exception { int g = (tint >> 8) & 0xFF; int b = tint & 0xFF; - var sb = new StringBuilder(); - sb.append(stack.getFluid().kjs$getId()); - DataComponentWrapper.writeVisualComponentsForCache(sb, ctx.registries().nbt(), stack.getComponents()); - - return renderCanvas(ctx, 16, sb, render -> { + return renderCanvas(req, 16, "fluid", CachedComponentObject.of(stack.getFluid(), stack.getComponentsPatch()).cacheKey(), render -> { var s = render.mc.getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(still); RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_BLOCKS); RenderSystem.setShader(GameRenderer::getPositionTexColorShader); @@ -202,28 +239,28 @@ public static HTTPResponse fluid(KJSHTTPRequest ctx) throws Exception { }); } - public static HTTPResponse itemTag(KJSHTTPRequest ctx) throws Exception { - var tagKey = ItemTags.create(ctx.id()); + public static HTTPResponse itemTag(KJSHTTPRequest req) throws Exception { + var tagKey = ItemTags.create(req.id()); - return renderCanvas(ctx, 16, new StringBuilder(), render -> { + return renderCanvas(req, 16, "item_tag", new StringBuilder(), render -> { // render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); render.graphics.renderFakeItem(Items.NAME_TAG.getDefaultInstance(), 0, 0, 0); }); } - public static HTTPResponse blockTag(KJSHTTPRequest ctx) throws Exception { - var tagKey = BlockTags.create(ctx.id()); + public static HTTPResponse blockTag(KJSHTTPRequest req) throws Exception { + var tagKey = BlockTags.create(req.id()); - return renderCanvas(ctx, 16, new StringBuilder(), render -> { + return renderCanvas(req, 16, "block_tag", new StringBuilder(), render -> { // render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); render.graphics.renderFakeItem(Items.NAME_TAG.getDefaultInstance(), 0, 0, 0); }); } - public static HTTPResponse fluidTag(KJSHTTPRequest ctx) throws Exception { - var tagKey = FluidTags.create(ctx.id()); + public static HTTPResponse fluidTag(KJSHTTPRequest req) throws Exception { + var tagKey = FluidTags.create(req.id()); - return renderCanvas(ctx, 16, new StringBuilder(), render -> { + return renderCanvas(req, 16, "fluid_tag", new StringBuilder(), render -> { // render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); render.graphics.renderFakeItem(Items.NAME_TAG.getDefaultInstance(), 0, 0, 0); }); diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java index c8823fd07..1e168ed0c 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java @@ -1,48 +1,151 @@ package dev.latvian.mods.kubejs.web.local.client; +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; import dev.latvian.apps.tinyserver.ServerRegistry; import dev.latvian.apps.tinyserver.http.response.HTTPResponse; import dev.latvian.apps.tinyserver.http.response.HTTPStatus; import dev.latvian.mods.kubejs.KubeJS; import dev.latvian.mods.kubejs.script.ScriptType; +import dev.latvian.mods.kubejs.util.CachedComponentObject; +import dev.latvian.mods.kubejs.util.Cast; +import dev.latvian.mods.kubejs.util.Lazy; import dev.latvian.mods.kubejs.web.JsonContent; import dev.latvian.mods.kubejs.web.KJSHTTPRequest; import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; public class KubeJSClientWeb { + public static final Lazy>> CACHED_ITEM_SEARCH = Lazy.of(() -> { + var map = new HashMap>(); + + for (var stack : BuiltInRegistries.CREATIVE_MODE_TAB.get(CreativeModeTabs.SEARCH).getSearchTabDisplayItems()) { + + var patch = stack.getComponentsPatch(); + + if (!patch.isEmpty()) { + map.put(UUID.randomUUID(), new CachedComponentObject<>(UUID.randomUUID(), stack.getItem(), patch)); + } + } + + return map; + }); + + public static final Lazy> REVERSE_CACHED_ITEM_SEARCH = Lazy.of(() -> { + var map = new HashMap(); + CACHED_ITEM_SEARCH.get().forEach((uuid, obj) -> map.put(obj, uuid)); + return map; + }); + public static void register(ServerRegistry registry) { - KubeJSWeb.addScriptTypeEndpoints(registry, ScriptType.CLIENT); + KubeJSWeb.addScriptTypeEndpoints(registry, ScriptType.CLIENT, KubeJS.getClientScriptManager()::reload); + + registry.get("/api/client/search/items", KubeJSClientWeb::getItemsResponse); + registry.get("/api/client/search/blocks", KubeJSClientWeb::getBlocksResponse); + registry.get("/api/client/search/fluids", KubeJSClientWeb::getFluidsResponse); + + registry.get("/api/client/assets/list/", KubeJSClientWeb::getAssetList); + registry.get("/api/client/assets/get/{namespace}/", KubeJSClientWeb::getAssetContent); + + registry.get("/img/{size}/item/{namespace}/{path}", ImageGenerator::item); + registry.get("/img/{size}/block/{namespace}/{path}", ImageGenerator::block); + registry.get("/img/{size}/fluid/{namespace}/{path}", ImageGenerator::fluid); + registry.get("/img/{size}/item-tag/{namespace}/{path}", ImageGenerator::itemTag); + registry.get("/img/{size}/block-tag/{namespace}/{path}", ImageGenerator::blockTag); + registry.get("/img/{size}/fluid-tag/{namespace}/{path}", ImageGenerator::fluidTag); + } + + private static HTTPResponse getItemsResponse(KJSHTTPRequest req) { + return HTTPResponse.ok().content(JsonContent.array(json -> { + var ops = Minecraft.getInstance().level == null ? req.registries().json() : Minecraft.getInstance().level.registryAccess().createSerializationContext(JsonOps.INSTANCE); + + for (var stack : BuiltInRegistries.CREATIVE_MODE_TAB.get(CreativeModeTabs.SEARCH).getSearchTabDisplayItems()) { + var o = new JsonObject(); + o.addProperty("id", stack.kjs$getId()); + o.addProperty("name", stack.getHoverName().getString()); + + var patch = stack.getComponentsPatch(); - registry.acceptPostTask("api/reload/client", KubeJS.getClientScriptManager()::reload); + if (!patch.isEmpty()) { + var p = new JsonObject(); - registry.get("assets/list/", KubeJSClientWeb::getAssetList); - registry.get("assets/get/{namespace}/", KubeJSClientWeb::getAssetContent); + try { + for (var entry : patch.entrySet()) { + var key = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(entry.getKey()).toString(); - registry.get("img/{size}/item/{namespace}/{path}", ImageGenerator::item); - registry.get("img/{size}/block/{namespace}/{path}", ImageGenerator::block); - registry.get("img/{size}/fluid/{namespace}/{path}", ImageGenerator::fluid); - registry.get("img/{size}/item-tag/{namespace}/{path}", ImageGenerator::itemTag); - registry.get("img/{size}/block-tag/{namespace}/{path}", ImageGenerator::blockTag); - registry.get("img/{size}/fluid-tag/{namespace}/{path}", ImageGenerator::fluidTag); + if (entry.getValue().isEmpty()) { + p.add(key, JsonNull.INSTANCE); + } else if (entry.getKey().codec() != null) { + p.add(key, entry.getKey().codec().encodeStart(ops, Cast.to(entry.getValue().get())).getOrThrow()); + } + } + + o.add("patch", p); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + json.add(o); + } + })); } - private static HTTPResponse getAssetList(KJSHTTPRequest ctx) { - var prefix = ctx.variables().get("prefix"); + private static HTTPResponse getBlocksResponse(KJSHTTPRequest req) { + return HTTPResponse.ok().content(JsonContent.array(json -> { + for (var block : BuiltInRegistries.BLOCK) { + var o = new JsonObject(); + o.addProperty("id", block.kjs$getId()); + o.addProperty("name", Component.translatable(block.getDescriptionId()).getString()); + json.add(o); + } + })); + } + + private static HTTPResponse getFluidsResponse(KJSHTTPRequest req) { + return HTTPResponse.ok().content(JsonContent.array(json -> { + for (var fluid : BuiltInRegistries.FLUID) { + var o = new JsonObject(); + o.addProperty("id", fluid.kjs$getId()); + o.addProperty("name", fluid.getFluidType().getDescription().getString()); + json.add(o); + } + })); + } + + private static HTTPResponse getAssetList(KJSHTTPRequest req) { + var prefix = req.variables().get("prefix"); if (prefix.isEmpty()) { return HTTPStatus.BAD_REQUEST; } - return HTTPResponse.ok().content(JsonContent.array(json -> { + return HTTPResponse.ok().content(JsonContent.object(json -> { for (var id : Minecraft.getInstance().getResourceManager().listResources(prefix, id -> true).keySet()) { - json.add(id.toString()); + var arr = (JsonArray) json.get(id.getNamespace()); + + if (arr == null) { + arr = new JsonArray(); + json.add(id.getNamespace(), arr); + } + + arr.add(id.getPath().substring(prefix.length() + 1)); } })); } - private static HTTPResponse getAssetContent(KJSHTTPRequest ctx) throws Exception { - var id = ctx.id(); + private static HTTPResponse getAssetContent(KJSHTTPRequest req) throws Exception { + var id = req.id(); var asset = Minecraft.getInstance().getResourceManager().getResource(id); if (asset.isEmpty()) { diff --git a/src/main/java/dev/latvian/mods/kubejs/web/local/client/MovedVertexConsumer.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/MovedVertexConsumer.java new file mode 100644 index 000000000..f02f84d0d --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/MovedVertexConsumer.java @@ -0,0 +1,36 @@ +package dev.latvian.mods.kubejs.web.local.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +public record MovedVertexConsumer(VertexConsumer parent, PoseStack.Pose pose) implements VertexConsumer { + @Override + public VertexConsumer addVertex(float x, float y, float z) { + return parent.addVertex(pose.pose(), x, y, z); + } + + @Override + public VertexConsumer setColor(int red, int green, int blue, int alpha) { + return parent.setColor(red, green, blue, alpha); + } + + @Override + public VertexConsumer setUv(float u, float v) { + return parent.setUv(u, v); + } + + @Override + public VertexConsumer setUv1(int u, int v) { + return parent.setUv1(u, v); + } + + @Override + public VertexConsumer setUv2(int u, int v) { + return parent.setUv2(u, v); + } + + @Override + public VertexConsumer setNormal(float normalX, float normalY, float normalZ) { + return parent.setNormal(pose, normalX, normalY, normalZ); + } +}