diff --git a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java index c3abfb825..4ef724525 100644 --- a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java +++ b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java @@ -147,6 +147,7 @@ import dev.latvian.mods.kubejs.util.JsonUtils; import dev.latvian.mods.kubejs.util.KubeResourceLocation; import dev.latvian.mods.kubejs.util.NBTIOWrapper; +import dev.latvian.mods.kubejs.util.NameProvider; import dev.latvian.mods.kubejs.util.NotificationToastData; import dev.latvian.mods.kubejs.util.RegExpKJS; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; @@ -161,23 +162,29 @@ import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import dev.latvian.mods.rhino.type.RecordTypeInfo; import dev.latvian.mods.rhino.type.TypeInfo; +import net.minecraft.Util; import net.minecraft.commands.arguments.selector.EntitySelector; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; import net.minecraft.core.Vec3i; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CollectionTag; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.ChatType; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.stats.Stat; import net.minecraft.stats.Stats; @@ -190,6 +197,7 @@ import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.entity.animal.WolfVariant; +import net.minecraft.world.entity.decoration.Painting; import net.minecraft.world.entity.decoration.PaintingVariant; import net.minecraft.world.item.ArmorItem; import net.minecraft.world.item.Item; @@ -205,6 +213,7 @@ import net.minecraft.world.item.component.Fireworks; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.ItemEnchantments; import net.minecraft.world.item.enchantment.providers.EnchantmentProvider; import net.minecraft.world.level.ItemLike; @@ -735,4 +744,76 @@ public void registerDataComponentTypeDescriptions(DataComponentTypeInfoRegistry public void registerLocalWebServer(LocalWebServerRegistry registry) { KubeJSWeb.register(registry); } + + @Override + public void registerItemNameProviders(NameProvider.Registry registry) { + registry.register(Items.ENCHANTED_BOOK, (registries, stack) -> { + var enchants = EnchantmentHelper.getEnchantmentsForCrafting(stack); + + if (enchants.isEmpty()) { + return null; + } + + var c = Component.empty(); + c.append(stack.getHoverName()); + boolean first = true; + + for (var e : enchants.entrySet()) { + if (first) { + first = false; + c.append(": "); + } else { + c.append(", "); + } + + c.append(Enchantment.getFullname(e.getKey(), e.getIntValue())); + } + + return c; + }); + + /* + registry.register(List.of(Items.POTION, Items.SPLASH_POTION, Items.LINGERING_POTION), (registries, stack) -> { + var potions = stack.get(DataComponents.POTION_CONTENTS); + + if (potions == null || potions.potion().isEmpty()) { + return null; + } + + var c = Component.empty(); + c.append(stack.getHoverName()); + // c.append(Potion.getName(potions.potion().get(), potions.)); + return c; + }); + */ + + for (var item : BuiltInRegistries.ITEM) { + var song = item.components().get(DataComponents.JUKEBOX_PLAYABLE); + + if (song != null) { + registry.register(item, (registries, stack) -> { + var key = Util.makeDescriptionId("jukebox_song", song.song().key().location()); + return Component.empty().append(stack.getHoverName()).append(": ").append(Component.translatable(key)); + }); + } + } + + registry.register(Items.PAINTING, (registries, stack) -> { + var customData = stack.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY); + + if (!customData.isEmpty()) { + var key = customData.read(registries.createSerializationContext(NbtOps.INSTANCE), Painting.VARIANT_MAP_CODEC) + .result() + .flatMap(Holder::unwrapKey) + .map(ResourceKey::location) + .orElse(null); + + if (key != null) { + return Component.empty().append(stack.getHoverName()).append(": ").append(Component.translatable(key.toLanguageKey("painting", "author"))).append(" - ").append(Component.translatable(key.toLanguageKey("painting", "title"))); + } + } + + return null; + }); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/plugin/KubeJSPlugin.java b/src/main/java/dev/latvian/mods/kubejs/plugin/KubeJSPlugin.java index daae8aceb..ab8dc3a37 100644 --- a/src/main/java/dev/latvian/mods/kubejs/plugin/KubeJSPlugin.java +++ b/src/main/java/dev/latvian/mods/kubejs/plugin/KubeJSPlugin.java @@ -24,11 +24,14 @@ import dev.latvian.mods.kubejs.script.TypeWrapperRegistry; import dev.latvian.mods.kubejs.server.DataExport; import dev.latvian.mods.kubejs.util.AttachedData; +import dev.latvian.mods.kubejs.util.NameProvider; import dev.latvian.mods.kubejs.web.LocalWebServerRegistry; import dev.latvian.mods.rhino.Context; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.RecipeHolder; import net.minecraft.world.level.Level; @@ -96,6 +99,9 @@ default void registerDataComponentTypeDescriptions(DataComponentTypeInfoRegistry default void registerLocalWebServer(LocalWebServerRegistry registry) { } + default void registerItemNameProviders(NameProvider.Registry registry) { + } + default void attachServerData(AttachedData event) { } diff --git a/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java b/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java index bfffa96bb..71fe307d4 100644 --- a/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java +++ b/src/main/java/dev/latvian/mods/kubejs/util/CachedComponentObject.java @@ -10,10 +10,12 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.material.Fluid; import net.neoforged.neoforge.fluids.FluidStack; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; import java.util.UUID; -public record CachedComponentObject, S>(UUID cacheKey, T value, S stack, DataComponentPatch components) { +public record CachedComponentObject, S>(UUID cacheKey, T value, S stack, DataComponentPatch components, Mutable iconPath) { public static > void writeCacheKey(FriendlyByteBuf buf, T value, DataComponentPatch components) { buf.writeUtf(value.kjs$getId()); buf.writeVarInt(components.size()); @@ -33,7 +35,7 @@ public static > void writeCacheKey(FriendlyByteBu public static , S> CachedComponentObject of(T value, S stack, DataComponentPatch components) { var buf = new FriendlyByteBuf(Unpooled.buffer()); writeCacheKey(buf, value, components); - return new CachedComponentObject<>(UUID.nameUUIDFromBytes(buf.array()), value, stack, components); + return new CachedComponentObject<>(UUID.nameUUIDFromBytes(buf.array()), value, stack, components, new MutableObject<>()); } public static CachedComponentObject ofItemStack(ItemStack stack, boolean visual) { diff --git a/src/main/java/dev/latvian/mods/kubejs/util/NameProvider.java b/src/main/java/dev/latvian/mods/kubejs/util/NameProvider.java new file mode 100644 index 000000000..fd1a2aec4 --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/util/NameProvider.java @@ -0,0 +1,31 @@ +package dev.latvian.mods.kubejs.util; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public interface NameProvider { + interface Registry { + void register(K key, NameProvider provider); + + default void register(List keys, NameProvider provider) { + for (var key : keys) { + register(key, provider); + } + } + } + + static Map> create(Consumer> registry) { + var map = new HashMap>(); + registry.accept(map::put); + return map; + } + + @Nullable + Component getName(RegistryAccess registries, T value); +} 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 12e08bb01..a15e26703 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPRequest.java @@ -3,6 +3,8 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.serialization.DynamicOps; import dev.latvian.apps.tinyserver.http.HTTPRequest; +import dev.latvian.apps.tinyserver.http.response.HTTPPayload; +import dev.latvian.apps.tinyserver.http.response.HTTPResponse; import dev.latvian.mods.kubejs.component.DataComponentWrapper; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import net.minecraft.core.component.DataComponentPatch; @@ -42,4 +44,9 @@ public DataComponentPatch components(DynamicOps ops) throws CommandSyntaxEx var str = query("components").asString(); return str.isEmpty() ? DataComponentPatch.EMPTY : DataComponentWrapper.patchOrEmptyOf(ops, "[" + str + "]"); } + + @Override + public HTTPResponse handleResponse(HTTPPayload payload, HTTPResponse response, @Nullable Throwable error) { + return response.cors(); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java b/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java index cc30096a7..350a0a0d6 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/LocalWebServer.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -44,6 +45,7 @@ public static void start(BlockableEventLoop eventLoop) { registry.server.setPort(WebServerProperties.get().port); registry.server.setMaxPortShift(10); registry.server.setMaxKeepAliveConnections(3); + registry.server.setKeepAliveTimeout(Duration.ofMinutes(5L)); int port = registry.server.start(); var url = "http://localhost:" + port; 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 d4e4c22cf..6c069671f 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 @@ -82,6 +82,9 @@ public class ImageGenerator { private record RenderImage(Minecraft mc, GuiGraphics graphics, int size) { } + public record CachedImage(HTTPResponse response, @Nullable String pathStr) { + } + private record BodyKey(byte[] bytes) { @Override public int hashCode() { @@ -108,11 +111,11 @@ public static TextureTarget getCanvas(int size) { return target; } - private static HTTPResponse renderCanvas(KJSHTTPRequest req, int canvasSize, String dir, @Nullable ByteBuf cacheBuf, boolean wildcard, Consumer render) { - int size = req.variable("size").asInt(); + private static CachedImage renderCanvas(KJSHTTPRequest req, int canvasSize, int imageSize, String dir, @Nullable ByteBuf cacheBuf, boolean wildcard, Consumer render) { + int size = imageSize > 0 ? imageSize : req.variable("size").asInt(); if (size < 1 || size > 1024) { - return HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"); + return new CachedImage(HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"), null); } if (req.query().containsKey("uncached")) { @@ -127,7 +130,8 @@ private static HTTPResponse renderCanvas(KJSHTTPRequest req, int canvasSize, Str 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)) { - return HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/')); + var pathStr = KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/'); + return new CachedImage(HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", pathStr), pathStr); } var bytes = req.supplyInMainThread(() -> { @@ -196,17 +200,18 @@ private static HTTPResponse renderCanvas(KJSHTTPRequest req, int canvasSize, Str } catch (Exception ignore) { } - return HTTPResponse.ok().content(bytes, "image/png").header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/')); + var pathStr = KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/'); + return new CachedImage(HTTPResponse.ok().content(bytes, "image/png").header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", pathStr), pathStr); } - return HTTPResponse.ok().content(bytes, "image/png"); + return new CachedImage(HTTPResponse.ok().content(bytes, "image/png"), null); } - private static HTTPResponse renderAnimated(KJSHTTPRequest req, String dir, @Nullable ByteBuf cacheBuf, List responses) throws Exception { + private static CachedImage renderAnimated(KJSHTTPRequest req, String dir, @Nullable ByteBuf cacheBuf, List images) throws Exception { int size = req.variable("size").asInt(); if (size < 1 || size > 1024) { - return HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"); + return new CachedImage(HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"), null); } if (req.query().containsKey("uncached")) { @@ -217,7 +222,8 @@ private static HTTPResponse renderAnimated(KJSHTTPRequest req, String dir, @Null var cachePath = cacheUUIDStr == null ? null : KubeJSPaths.dir(KubeJSPaths.LOCAL.resolve("cache/web/img/" + dir + "/" + cacheUUIDStr.substring(0, 2))).resolve(cacheUUIDStr + "_" + size + ".gif"); if (cachePath != null && Files.exists(cachePath)) { - return HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/')); + var pathStr = KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/'); + return new CachedImage(HTTPResponse.ok().content(cachePath).header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", pathStr), pathStr); } var outputStream = new ByteArrayOutputStream(); @@ -233,10 +239,10 @@ private static HTTPResponse renderAnimated(KJSHTTPRequest req, String dir, @Null var bodyKeys = new HashSet(); req.runInMainThread(() -> { - for (var response : responses) { + for (var image : images) { try { var content = new ContentGrabber(LocalWebServer.SERVER_NAME, req.startTime()); - response.build(content); + image.response().build(content); if (content.body != null && bodyKeys.add(new BodyKey(content.body))) { encoder.addFrame(ImageIO.read(new ByteArrayInputStream(content.body))); @@ -256,27 +262,38 @@ private static HTTPResponse renderAnimated(KJSHTTPRequest req, String dir, @Null } catch (Exception ignore) { } - return HTTPResponse.ok().content(bytes, "image/gif").header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/')); + var pathStr = KubeJSPaths.GAMEDIR.relativize(cachePath).toString().replace('\\', '/'); + return new CachedImage(HTTPResponse.ok().content(bytes, "image/gif").header("X-KubeJS-Cache-Key", cacheUUIDStr).header("X-KubeJS-Cache-Path", pathStr), pathStr); + } + + return new CachedImage(HTTPResponse.ok().content(bytes, "image/gif"), null); + } + + public static HTTPResponse renderAllItems(KJSHTTPRequest req) throws Exception { + int size = req.variable("size").asInt(); + + if (size < 1 || size > 1024) { + return HTTPStatus.BAD_REQUEST.text("Invalid size, must be [1, 1024]"); } - return HTTPResponse.ok().content(bytes, "image/gif"); + return HTTPResponse.noContent(); } public static HTTPResponse item(KJSHTTPRequest req) throws Exception { var stack = BuiltInRegistries.ITEM.get(req.id()).getDefaultInstance(); stack.applyComponents(req.components(req.registries().nbt())); - return renderItem(req, stack, req.query().containsKey("wildcard")); + return renderItem(req, 0, stack, req.query().containsKey("wildcard")).response(); } - public static HTTPResponse renderItem(KJSHTTPRequest req, ItemStack stack, boolean wildcard) { + public static CachedImage renderItem(KJSHTTPRequest req, int imageSize, ItemStack stack, boolean wildcard) { if (stack.isEmpty()) { - return HTTPStatus.NOT_FOUND; + return new CachedImage(HTTPStatus.NOT_FOUND, null); } var buf = new FriendlyByteBuf(Unpooled.buffer()); CachedComponentObject.writeCacheKey(buf, stack.getItem(), DataComponentWrapper.visualPatch(stack.getComponentsPatch())); - return renderCanvas(req, 16, "item", buf, wildcard, render -> { + return renderCanvas(req, 16, imageSize, "item", buf, wildcard, render -> { render.graphics.renderFakeItem(stack, 0, 0, 0); render.graphics.renderItemDecorations(render.mc.font, stack, 0, 0); }); @@ -284,12 +301,12 @@ public static HTTPResponse renderItem(KJSHTTPRequest req, ItemStack stack, boole public static HTTPResponse block(KJSHTTPRequest req) throws Exception { var state = BlockWrapper.withProperties(BuiltInRegistries.BLOCK.get(req.id()).defaultBlockState(), req.query()); - return renderBlock(req, state, req.query().containsKey("wildcard")); + return renderBlock(req, state, req.query().containsKey("wildcard")).response(); } - public static HTTPResponse renderBlock(KJSHTTPRequest req, BlockState state, boolean wildcard) { + public static CachedImage renderBlock(KJSHTTPRequest req, BlockState state, boolean wildcard) { if (state.isEmpty()) { - return HTTPStatus.NOT_FOUND; + return new CachedImage(HTTPStatus.NOT_FOUND, null); } var buf = new FriendlyByteBuf(Unpooled.buffer()); @@ -308,7 +325,7 @@ public static HTTPResponse renderBlock(KJSHTTPRequest req, BlockState state, boo } } - return renderCanvas(req, 16, "block", buf, wildcard, render -> { + return renderCanvas(req, 16, 0, "block", buf, wildcard, render -> { var model = render.mc.getBlockRenderer().getBlockModel(state); var pose = render.graphics.pose(); pose.pushPose(); @@ -352,12 +369,12 @@ public static HTTPResponse renderBlock(KJSHTTPRequest req, BlockState state, boo 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())); - return renderFluid(req, stack, req.query().containsKey("wildcard")); + return renderFluid(req, stack, req.query().containsKey("wildcard")).response(); } - public static HTTPResponse renderFluid(KJSHTTPRequest req, FluidStack stack, boolean wildcard) { + public static CachedImage renderFluid(KJSHTTPRequest req, FluidStack stack, boolean wildcard) { if (stack.isEmpty()) { - return HTTPStatus.NOT_FOUND; + return new CachedImage(HTTPStatus.NOT_FOUND, null); } var fluidInfo = IClientFluidTypeExtensions.of(stack.getFluid()); @@ -371,7 +388,7 @@ public static HTTPResponse renderFluid(KJSHTTPRequest req, FluidStack stack, boo var buf = new FriendlyByteBuf(Unpooled.buffer()); CachedComponentObject.writeCacheKey(buf, stack.getFluid(), DataComponentWrapper.visualPatch(stack.getComponentsPatch())); - return renderCanvas(req, 16, "fluid", buf, wildcard, render -> { + return renderCanvas(req, 16, 0, "fluid", buf, wildcard, render -> { var s = render.mc.getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(still); RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_BLOCKS); RenderSystem.setShader(GameRenderer::getPositionTexColorShader); @@ -393,14 +410,14 @@ public static HTTPResponse itemTag(KJSHTTPRequest req) throws Exception { } var buf = new FriendlyByteBuf(Unpooled.buffer()); - var list = new ArrayList(); + var list = new ArrayList(); for (var holder : tag.get()) { buf.writeUtf(holder.value().kjs$getId()); - list.add(renderItem(req, holder.value().getDefaultInstance(), true)); + list.add(renderItem(req, 0, holder.value().getDefaultInstance(), true)); } - return renderAnimated(req, "item_tag", buf, list); + return renderAnimated(req, "item_tag", buf, list).response(); } public static HTTPResponse blockTag(KJSHTTPRequest req) throws Exception { @@ -411,7 +428,7 @@ public static HTTPResponse blockTag(KJSHTTPRequest req) throws Exception { } var buf = new FriendlyByteBuf(Unpooled.buffer()); - var list = new ArrayList(); + var list = new ArrayList(); for (var holder : tag.get()) { buf.writeUtf(holder.value().kjs$getId()); @@ -419,13 +436,13 @@ public static HTTPResponse blockTag(KJSHTTPRequest req) throws Exception { var item = holder.value().asItem(); if (item != Items.AIR) { - list.add(renderItem(req, item.getDefaultInstance(), true)); + list.add(renderItem(req, 0, item.getDefaultInstance(), true)); } else { list.add(renderBlock(req, holder.value().defaultBlockState(), true)); } } - return renderAnimated(req, "block_tag", buf, list); + return renderAnimated(req, "block_tag", buf, list).response(); } public static HTTPResponse fluidTag(KJSHTTPRequest req) throws Exception { @@ -436,14 +453,14 @@ public static HTTPResponse fluidTag(KJSHTTPRequest req) throws Exception { } var buf = new FriendlyByteBuf(Unpooled.buffer()); - var list = new ArrayList(); + var list = new ArrayList(); for (var holder : tag.get()) { buf.writeUtf(holder.value().kjs$getId()); list.add(renderFluid(req, new FluidStack(holder, FluidType.BUCKET_VOLUME), true)); } - return renderAnimated(req, "fluid_tag", buf, list); + return renderAnimated(req, "fluid_tag", buf, list).response(); } private static class ContentGrabber extends HTTPPayload { 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 5066d00b3..00d9d1c6d 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 @@ -7,12 +7,16 @@ import com.mojang.serialization.JsonOps; import dev.latvian.apps.tinyserver.http.response.HTTPResponse; import dev.latvian.apps.tinyserver.http.response.HTTPStatus; +import dev.latvian.apps.tinyserver.http.response.error.client.BadRequestError; import dev.latvian.mods.kubejs.KubeJS; +import dev.latvian.mods.kubejs.KubeJSPaths; import dev.latvian.mods.kubejs.bindings.UUIDWrapper; +import dev.latvian.mods.kubejs.plugin.KubeJSPlugins; 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.util.NameProvider; import dev.latvian.mods.kubejs.web.JsonContent; import dev.latvian.mods.kubejs.web.KJSHTTPRequest; import dev.latvian.mods.kubejs.web.LocalWebServer; @@ -20,36 +24,57 @@ import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.language.I18n; +import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.NbtOps; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; public class KubeJSClientWeb { - public static final Lazy>> CACHED_ITEM_SEARCH = Lazy.of(() -> { + private static final Lazy SEARCH_TAB = Lazy.of(() -> BuiltInRegistries.CREATIVE_MODE_TAB.get(CreativeModeTabs.SEARCH)); + + private static final Lazy>> ITEM_NAME_PROVIDERS = Lazy.of(() -> NameProvider.create(reg -> KubeJSPlugins.forEachPlugin(p -> p.registerItemNameProviders(reg)))); + + public static Map> createItemSearch(boolean useSearchTab) { var map = new LinkedHashMap>(); - for (var stack : BuiltInRegistries.CREATIVE_MODE_TAB.get(CreativeModeTabs.SEARCH).getSearchTabDisplayItems()) { - var patch = stack.getComponentsPatch(); - map.put(UUID.randomUUID(), new CachedComponentObject<>(UUID.randomUUID(), stack.getItem(), stack, patch)); + if (useSearchTab) { + for (var stack : SEARCH_TAB.get().getSearchTabDisplayItems()) { + var obj = CachedComponentObject.ofItemStack(stack, false); + map.put(obj.cacheKey(), obj); + } + } else { + for (var item : BuiltInRegistries.ITEM) { + if (item != Items.AIR) { + var obj = CachedComponentObject.ofItemStack(item.getDefaultInstance(), false); + map.put(obj.cacheKey(), obj); + } + } } 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)); + public static Map, UUID> createReverseItemSearch(Map> original) { + var map = new HashMap, UUID>(); + original.forEach((uuid, obj) -> map.put(obj, uuid)); return map; - }); + } public static void register(LocalWebServerRegistry registry) { KubeJSWeb.addScriptTypeEndpoints(registry, ScriptType.CLIENT, KubeJS.getClientScriptManager()::reload); @@ -103,18 +128,60 @@ private static HTTPResponse getComponentString(KJSHTTPRequest req) { } private static HTTPResponse getSearchItems(KJSHTTPRequest req) { + var level = Minecraft.getInstance().level; + var jsonOps = level == null ? req.registries().json() : level.registryAccess().createSerializationContext(JsonOps.INSTANCE); + var nbtOps = level == null ? req.registries().nbt() : level.registryAccess().createSerializationContext(NbtOps.INSTANCE); + var results = new JsonArray(); + var itemSearch = createItemSearch(level != null); + var search = req.query("search").asString().toLowerCase(); + var includeTags = req.query("tags").asBoolean(false); + var renderIcons = req.query("render-icons").asInt(0); + + if (renderIcons > 1024) { + throw new BadRequestError("render-icons value too large, max 1024!"); + } + + var localPath = "local/kubejs/cache/web/img/item/"; + var iconPathRoot = renderIcons > 0 ? (KubeJSPaths.GAMEDIR.toUri() + localPath) : (LocalWebServer.instance().url() + "/img/64/item/"); + + if (renderIcons > 0) { + var futures = new ArrayList>(itemSearch.size()); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + for (var item : itemSearch.values()) { + futures.add(CompletableFuture.runAsync(() -> item.iconPath().setValue(ImageGenerator.renderItem(req, renderIcons, item.stack(), false).pathStr()), executor)); + } + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } + } + + var registries = level != null ? level.registryAccess() : RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY); + return HTTPResponse.ok().content(JsonContent.object(json -> { - var jsonOps = Minecraft.getInstance().level == null ? req.registries().json() : Minecraft.getInstance().level.registryAccess().createSerializationContext(JsonOps.INSTANCE); - var nbtOps = Minecraft.getInstance().level == null ? req.registries().nbt() : Minecraft.getInstance().level.registryAccess().createSerializationContext(NbtOps.INSTANCE); - var results = new JsonArray(); - var iconPathRoot = LocalWebServer.instance().url() + "/img/64/item/"; + json.addProperty("world", level != null); - for (var item : CACHED_ITEM_SEARCH.get().values()) { + for (var item : itemSearch.values()) { var o = new JsonObject(); + + var nameProvider = ITEM_NAME_PROVIDERS.get().get(item.value()); + var nameProviderName = nameProvider == null ? null : nameProvider.getName(registries, item.stack()); + var name = (nameProviderName == null ? item.stack().getHoverName() : nameProviderName).getString(); + + if (!search.isEmpty() && !name.toLowerCase().contains(search)) { + continue; + } + o.addProperty("cache_key", UUIDWrapper.toString(item.cacheKey())); o.addProperty("id", item.value().kjs$getId()); - o.addProperty("name", item.stack().getHoverName().getString()); - o.addProperty("icon_path", item.stack().kjs$getWebIconURL(nbtOps, 64).fullString().substring(iconPathRoot.length())); + o.addProperty("name", name); + o.addProperty("icon_path", renderIcons > 0 ? item.iconPath().getValue().substring(localPath.length()) : item.stack().kjs$getWebIconURL(nbtOps, 64).fullString().substring(iconPathRoot.length())); + + var block = item.value() instanceof BlockItem b ? b.getBlock() : Blocks.AIR; + + if (block != Blocks.AIR) { + o.addProperty("block", block.kjs$getId()); + } var patch = item.components(); @@ -138,14 +205,16 @@ private static HTTPResponse getSearchItems(KJSHTTPRequest req) { } } - var tags = new JsonArray(); + if (includeTags) { + var tags = new JsonArray(); - for (var t : item.value().builtInRegistryHolder().tags().toList()) { - tags.add(t.location().toString()); - } + for (var t : item.value().builtInRegistryHolder().tags().toList()) { + tags.add(t.location().toString()); + } - if (!tags.isEmpty()) { - o.add("tags", tags); + if (!tags.isEmpty()) { + o.add("tags", tags); + } } results.add(o); @@ -157,20 +226,24 @@ private static HTTPResponse getSearchItems(KJSHTTPRequest req) { } private static HTTPResponse getSearchBlocks(KJSHTTPRequest req) { + var includeTags = req.query("tags").asBoolean(false); + 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()); - var tags = new JsonArray(); + if (includeTags) { + var tags = new JsonArray(); - for (var t : block.builtInRegistryHolder().tags().toList()) { - tags.add(t.location().toString()); - } + for (var t : block.builtInRegistryHolder().tags().toList()) { + tags.add(t.location().toString()); + } - if (!tags.isEmpty()) { - o.add("tags", tags); + if (!tags.isEmpty()) { + o.add("tags", tags); + } } json.add(o); @@ -179,20 +252,24 @@ private static HTTPResponse getSearchBlocks(KJSHTTPRequest req) { } private static HTTPResponse getSearchFluids(KJSHTTPRequest req) { + var includeTags = req.query("tags").asBoolean(false); + 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()); - var tags = new JsonArray(); + if (includeTags) { + var tags = new JsonArray(); - for (var t : fluid.builtInRegistryHolder().tags().toList()) { - tags.add(t.location().toString()); - } + for (var t : fluid.builtInRegistryHolder().tags().toList()) { + tags.add(t.location().toString()); + } - if (!tags.isEmpty()) { - o.add("tags", tags); + if (!tags.isEmpty()) { + o.add("tags", tags); + } } json.add(o);