diff --git a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java index 5713c1a2f..e881dca26 100644 --- a/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java +++ b/src/main/java/dev/latvian/mods/kubejs/BuiltinKubeJSPlugin.java @@ -152,6 +152,9 @@ import dev.latvian.mods.kubejs.util.Tristate; import dev.latvian.mods.kubejs.util.UtilsJS; import dev.latvian.mods.kubejs.util.registrypredicate.RegistryPredicate; +import dev.latvian.mods.kubejs.web.KJSHTTPContext; +import dev.latvian.mods.kubejs.web.WebServerRegistry; +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.commands.arguments.selector.EntitySelector; @@ -704,4 +707,9 @@ public void clearCaches() { public void registerDataComponentTypeDescriptions(DataComponentTypeInfoRegistry registry) { // DataComponents.ATTRIBUTE_MODIFIERS } + + @Override + public void registerLocalWebServer(WebServerRegistry registry) { + KubeJSWeb.register(registry); + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/KubeJS.java b/src/main/java/dev/latvian/mods/kubejs/KubeJS.java index a3ba86263..ab43e274c 100644 --- a/src/main/java/dev/latvian/mods/kubejs/KubeJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/KubeJS.java @@ -20,6 +20,8 @@ import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.kubejs.script.data.KubeFileResourcePack; import dev.latvian.mods.kubejs.util.RecordDefaults; +import dev.latvian.mods.kubejs.web.KubeJSLocalWebServer; +import dev.latvian.mods.kubejs.web.WebServerProperties; import net.minecraft.resources.ResourceLocation; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; @@ -76,19 +78,19 @@ public KubeJS(IEventBus bus, Dist dist, ModContainer mod) throws Throwable { try { Files.writeString(KubeJSPaths.README, """ Find out more info on the website: https://kubejs.com/ - + Directory information: - + assets - Acts as a resource pack, you can put any client resources in here, like textures, models, etc. Example: assets/kubejs/textures/item/test_item.png data - Acts as a datapack, you can put any server resources in here, like loot tables, functions, etc. Example: data/kubejs/loot_tables/blocks/test_block.json - + startup_scripts - Scripts that get loaded once during game startup - Used for adding items and other things that can only happen while the game is loading (Can be reloaded with /kubejs reload_startup_scripts, but it may not work!) server_scripts - Scripts that get loaded every time server resources reload - Used for modifying recipes, tags, loot tables, and handling server events (Can be reloaded with /reload) client_scripts - Scripts that get loaded every time client resources reload - Used for JEI events, tooltips and other client side things (Can be reloaded with F3+T) - + config - KubeJS config storage. This is also the only directory that scripts can access other than world directory exported - Data dumps like texture atlases end up here - + You can find type-specific logs in logs/kubejs/ directory """.trim() ); @@ -159,5 +161,9 @@ public KubeJS(IEventBus bus, Dist dist, ModContainer mod) throws Throwable { StartupEvents.INIT.post(ScriptType.STARTUP, KubeStartupEvent.BASIC); // KubeJSRegistries.chunkGenerators().register(new ResourceLocation(KubeJS.MOD_ID, "flat"), () -> KJSFlatLevelSource.CODEC); + + if (!datagen && WebServerProperties.get().enabled && (dist == Dist.CLIENT || !WebServerProperties.get().publicAddress.isEmpty())) { + KubeJSLocalWebServer.start(); + } } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/KubeJSCommon.java b/src/main/java/dev/latvian/mods/kubejs/KubeJSCommon.java index 7ed7286dc..22f585e5d 100644 --- a/src/main/java/dev/latvian/mods/kubejs/KubeJSCommon.java +++ b/src/main/java/dev/latvian/mods/kubejs/KubeJSCommon.java @@ -62,4 +62,8 @@ public void runInMainThread(Runnable runnable) { public void updateServerData(KubeServerData data) { } + + public String getWebServerWindowTitle() { + return "Dedicated Server"; + } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java b/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java index b6b904e5f..82e17006d 100644 --- a/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java +++ b/src/main/java/dev/latvian/mods/kubejs/KubeJSPaths.java @@ -47,6 +47,7 @@ static Path dir(Path dir) { 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/client/BuiltinKubeJSClientPlugin.java b/src/main/java/dev/latvian/mods/kubejs/client/BuiltinKubeJSClientPlugin.java index 745fd68c2..b5defad91 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/BuiltinKubeJSClientPlugin.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/BuiltinKubeJSClientPlugin.java @@ -2,7 +2,6 @@ import dev.latvian.mods.kubejs.KubeJS; import dev.latvian.mods.kubejs.bindings.event.ClientEvents; -import dev.latvian.mods.kubejs.client.web.KubeJSWeb; import dev.latvian.mods.kubejs.event.EventGroupRegistry; import dev.latvian.mods.kubejs.plugin.KubeJSPlugin; import dev.latvian.mods.kubejs.script.BindingRegistry; @@ -10,6 +9,7 @@ import dev.latvian.mods.kubejs.util.ScheduledEvents; import dev.latvian.mods.kubejs.web.KJSHTTPContext; import dev.latvian.mods.kubejs.web.WebServerRegistry; +import dev.latvian.mods.kubejs.web.local.client.KubeJSClientWeb; import net.minecraft.client.Minecraft; import net.neoforged.fml.ModList; @@ -35,7 +35,7 @@ public void registerBindings(BindingRegistry bindings) { @Override public void registerLocalWebServer(WebServerRegistry registry) { - KubeJSWeb.register(registry); + KubeJSClientWeb.register(registry); } @Override diff --git a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSClient.java b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSClient.java index 6337c8995..d7c766083 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSClient.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSClient.java @@ -193,6 +193,12 @@ public void updateServerData(KubeServerData data) { } } + @Override + public String getWebServerWindowTitle() { + var mc = Minecraft.getInstance(); + return mc.getGameProfile().getName() + ", " + mc.kjs$getTitle(); + } + public static void loadPostChains(Minecraft mc) { KubedexHighlight.INSTANCE.loadPostChains(mc); } diff --git a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSErrorScreen.java b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSErrorScreen.java index 06404ca8f..fd35edaf9 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSErrorScreen.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSErrorScreen.java @@ -239,7 +239,7 @@ public void render(GuiGraphics g, int idx, int y, int x, int w, int h, int mx, i var lines = new ArrayList(); for (var line : line.sourceLines) { - lines.add(Component.literal(line.toString()).getVisualOrderText()); + lines.add(Component.empty().append(Component.literal(line.source()).kjs$gray()).append("#" + line.line()).getVisualOrderText()); } errorList.screen.setTooltipForNextRenderPass(lines); diff --git a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java index 99963c9d0..8c4ab2780 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java +++ b/src/main/java/dev/latvian/mods/kubejs/client/KubeJSModClientEventHandler.java @@ -16,12 +16,9 @@ import dev.latvian.mods.kubejs.item.ModifyItemTooltipsKubeEvent; import dev.latvian.mods.kubejs.kubedex.KubedexHighlight; import dev.latvian.mods.kubejs.registry.RegistryObjectStorage; -import dev.latvian.mods.kubejs.script.PlatformWrapper; import dev.latvian.mods.kubejs.script.ScriptType; import dev.latvian.mods.kubejs.text.tooltip.ItemTooltipData; import dev.latvian.mods.kubejs.util.ID; -import dev.latvian.mods.kubejs.web.KubeJSLocalWebServer; -import dev.latvian.mods.kubejs.web.WebServerProperties; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.ItemBlockRenderTypes; @@ -102,10 +99,6 @@ private static void setupClient0() { var list = new ArrayList(); ItemEvents.MODIFY_TOOLTIPS.post(ScriptType.CLIENT, new ModifyItemTooltipsKubeEvent(list::add)); KubeJSClient.clientItemTooltips = List.copyOf(list); - - if (!PlatformWrapper.isGeneratingData() && WebServerProperties.get().enabled) { - KubeJSLocalWebServer.start(); - } } @SubscribeEvent 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 fab0a7177..2baea9f07 100644 --- a/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java +++ b/src/main/java/dev/latvian/mods/kubejs/component/DataComponentWrapper.java @@ -29,7 +29,9 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; +import java.util.HashSet; import java.util.Map; +import java.util.Set; public interface DataComponentWrapper { DynamicCommandExceptionType ERROR_UNKNOWN_COMPONENT = new DynamicCommandExceptionType((object) -> Component.translatableEscape("arguments.item.component.unknown", object)); @@ -58,6 +60,32 @@ public interface DataComponentWrapper { return Map.copyOf(map); }); + Lazy>> VISUAL_DIFFERENCE = Lazy.of(() -> { + var set = new HashSet>(); + + set.add(DataComponents.DAMAGE); + set.add(DataComponents.MAX_DAMAGE); + set.add(DataComponents.ENCHANTMENTS); + set.add(DataComponents.STORED_ENCHANTMENTS); + set.add(DataComponents.CUSTOM_MODEL_DATA); + set.add(DataComponents.ENCHANTMENT_GLINT_OVERRIDE); + set.add(DataComponents.DYED_COLOR); + set.add(DataComponents.MAP_COLOR); + set.add(DataComponents.POTION_CONTENTS); + set.add(DataComponents.TRIM); + set.add(DataComponents.ENTITY_DATA); + set.add(DataComponents.BLOCK_ENTITY_DATA); + set.add(DataComponents.FIREWORK_EXPLOSION); + set.add(DataComponents.FIREWORKS); + set.add(DataComponents.PROFILE); + set.add(DataComponents.BANNER_PATTERNS); + set.add(DataComponents.BASE_COLOR); + set.add(DataComponents.POT_DECORATIONS); + set.add(DataComponents.BLOCK_STATE); + + return set; + }); + static TypeInfo getTypeInfo(DataComponentType type) { return TYPE_INFOS.get().getOrDefault(type, TypeInfo.NONE); } @@ -292,4 +320,23 @@ static StringBuilder patchToString(StringBuilder builder, DynamicOps dynami builder.append(']'); 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()); + } + } + } + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentException.java b/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentException.java index 0a59700c8..e36f2691e 100644 --- a/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentException.java +++ b/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentException.java @@ -8,5 +8,7 @@ public class EmptyRecipeComponentException extends KubeRuntimeException { public EmptyRecipeComponentException(RecipeComponent component) { super("Component '" + component + "' is not allowed to be empty!"); this.component = component; + + customData("invalid_component", component.toString()); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentValueException.java b/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentValueException.java index 25963b0b1..53cc05ed6 100644 --- a/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentValueException.java +++ b/src/main/java/dev/latvian/mods/kubejs/error/EmptyRecipeComponentValueException.java @@ -8,5 +8,7 @@ public class EmptyRecipeComponentValueException extends KubeRuntimeException { public EmptyRecipeComponentValueException(RecipeComponent component) { super("Component '" + component + "' is not allowed to contain empty values!"); this.component = component; + + customData("invalid_component", component.toString()); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/error/EmptyTagException.java b/src/main/java/dev/latvian/mods/kubejs/error/EmptyTagException.java deleted file mode 100644 index 2bd20b9a7..000000000 --- a/src/main/java/dev/latvian/mods/kubejs/error/EmptyTagException.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.latvian.mods.kubejs.error; - -import net.minecraft.tags.TagKey; - -public class EmptyTagException extends KubeRuntimeException { - public final TagKey tagKey; - - public EmptyTagException(TagKey tagKey) { - super("Empty tag: " + tagKey.location()); - this.tagKey = tagKey; - } -} diff --git a/src/main/java/dev/latvian/mods/kubejs/error/InvalidRecipeComponentException.java b/src/main/java/dev/latvian/mods/kubejs/error/InvalidRecipeComponentException.java index e0fcfc999..c46de92c3 100644 --- a/src/main/java/dev/latvian/mods/kubejs/error/InvalidRecipeComponentException.java +++ b/src/main/java/dev/latvian/mods/kubejs/error/InvalidRecipeComponentException.java @@ -8,5 +8,8 @@ public class InvalidRecipeComponentException extends KubeRuntimeException { public InvalidRecipeComponentException(RecipeComponentValue h, Throwable cause) { super("Invalid component '" + h.key.name + "' (" + h.key.component + ")", cause); this.componentValueHolder = h; + + customData("key_name", h.key.name); + customData("key_component", h.key.component.toString()); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/error/KubeRuntimeException.java b/src/main/java/dev/latvian/mods/kubejs/error/KubeRuntimeException.java index 9ea0d54c5..735448439 100644 --- a/src/main/java/dev/latvian/mods/kubejs/error/KubeRuntimeException.java +++ b/src/main/java/dev/latvian/mods/kubejs/error/KubeRuntimeException.java @@ -1,10 +1,17 @@ package dev.latvian.mods.kubejs.error; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import dev.latvian.mods.kubejs.script.ConsoleLine; import dev.latvian.mods.kubejs.script.SourceLine; import dev.latvian.mods.kubejs.util.MutedError; +import dev.latvian.mods.rhino.RhinoException; public class KubeRuntimeException extends RuntimeException implements MutedError { - public SourceLine sourceLine; + private SourceLine sourceLine; + private JsonObject customData; public KubeRuntimeException(String m) { super(m); @@ -45,4 +52,41 @@ public KubeRuntimeException source(SourceLine sourceLine) { return this; } + + public KubeRuntimeException customData(String key, JsonElement data) { + if (customData == null) { + customData = new JsonObject(); + } + + customData.add(key, data); + return this; + } + + public KubeRuntimeException customData(String key, String value) { + return customData(key, value == null ? JsonNull.INSTANCE : new JsonPrimitive(value)); + } + + public void apply(ConsoleLine line) { + Throwable c = this; + + while (c != null) { + if (c instanceof KubeRuntimeException ex) { + line.withSourceLine(ex.sourceLine); + + if (ex.customData != null) { + for (var entry : ex.customData.entrySet()) { + line.customData(entry.getKey(), entry.getValue(), false); + } + } + } + + if (c instanceof RhinoException ex) { + if (ex.lineSource() != null) { + line.withSourceLine(ex.lineSource(), ex.lineNumber()); + } + } + + c = c.getCause(); + } + } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java index e8dab7e11..6b1a56259 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleJS.java @@ -1,5 +1,6 @@ package dev.latvian.mods.kubejs.script; +import com.google.gson.JsonArray; import dev.latvian.mods.kubejs.DevProperties; import dev.latvian.mods.kubejs.bindings.TextIcons; import dev.latvian.mods.kubejs.error.KubeRuntimeException; @@ -12,6 +13,7 @@ import dev.latvian.mods.kubejs.util.WrappedJS; import dev.latvian.mods.kubejs.web.KJSHTTPContext; import dev.latvian.mods.kubejs.web.http.HTTPResponse; +import dev.latvian.mods.kubejs.web.http.SimpleHTTPResponse; import dev.latvian.mods.kubejs.web.ws.WSHandler; import dev.latvian.mods.rhino.Context; import dev.latvian.mods.rhino.ContextFactory; @@ -176,7 +178,7 @@ private ConsoleLine line(LogType type, SourceLine sourceLine, Object object, @Nu line.withSourceLine(sourceLine); if (error instanceof KubeRuntimeException ex) { - line.withSourceLine(ex.sourceLine); + ex.apply(line); } if (error instanceof RhinoException ex) { @@ -604,10 +606,26 @@ public int compareTo(VarFunc o) { } public HTTPResponse getErrorsResponse(KJSHTTPContext ctx) { - return HTTPResponse.NO_CONTENT; + return SimpleHTTPResponse.lazyJson(() -> { + var json = new JsonArray(); + + for (var error : errors) { + json.add(error.toJson()); + } + + return json; + }); } public HTTPResponse getWarningsResponse(KJSHTTPContext ctx) { - return HTTPResponse.NO_CONTENT; + return SimpleHTTPResponse.lazyJson(() -> { + var json = new JsonArray(); + + for (var error : warnings) { + json.add(error.toJson()); + } + + return json; + }); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleLine.java b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleLine.java index c4d1a0569..ef12f1937 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ConsoleLine.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ConsoleLine.java @@ -1,6 +1,7 @@ package dev.latvian.mods.kubejs.script; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import dev.latvian.mods.kubejs.util.LogType; import net.minecraft.network.FriendlyByteBuf; @@ -49,6 +50,7 @@ public void encode(FriendlyByteBuf buf, ConsoleLine line) { public Path externalFile = null; public List stackTrace = List.of(); private String cachedText; + private JsonObject customData; public ConsoleLine(ConsoleJS console, long timestamp, String message) { this.console = console; @@ -125,19 +127,42 @@ public String toString() { return getText(); } + public ConsoleLine customData(String key, JsonElement data, boolean override) { + if (customData == null) { + customData = new JsonObject(); + } + + if (override || !customData.has(key)) { + customData.add(key, data); + } + + return this; + } + public JsonObject toJson() { var json = new JsonObject(); json.addProperty("type", type.id); json.addProperty("message", getText()); json.addProperty("timestamp", timestamp); - var sls = new JsonArray(); - json.add("source_lines", sls); + + if (customData != null) { + json.add("custom_data", customData); + } + + var ssls = new JsonArray(); + var asls = new JsonArray(); + json.add("script_source_lines", ssls); + json.add("all_source_lines", asls); for (var l : sourceLines) { var sourceLine = new JsonObject(); sourceLine.addProperty("source", l.source()); sourceLine.addProperty("line", l.line()); - sls.add(sourceLine); + asls.add(sourceLine); + + if (l.source().endsWith(".js")) { + ssls.add(sourceLine); + } } var st = new JsonArray(); 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 24dde375d..e61346ba5 100644 --- a/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java +++ b/src/main/java/dev/latvian/mods/kubejs/script/ScriptManager.java @@ -1,5 +1,6 @@ package dev.latvian.mods.kubejs.script; +import com.google.gson.JsonObject; import dev.latvian.mods.kubejs.DevProperties; import dev.latvian.mods.kubejs.KubeJS; import dev.latvian.mods.kubejs.plugin.ClassFilter; @@ -7,6 +8,7 @@ import dev.latvian.mods.kubejs.plugin.KubeJSPlugins; import dev.latvian.mods.kubejs.util.LogType; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; +import dev.latvian.mods.kubejs.web.local.KubeJSWeb; import java.io.File; import java.io.IOException; @@ -185,13 +187,24 @@ private void load() { } loadAdditional(); - scriptType.console.info("Loaded " + i + "/" + t + " KubeJS " + scriptType.name + " scripts in " + (System.currentTimeMillis() - startAll) / 1000D + " s with " + scriptType.console.errors.size() + " errors and " + scriptType.console.warnings.size() + " warnings"); + long ms = System.currentTimeMillis() - 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; if (!watchingFiles.isEmpty() && DevProperties.get().reloadOnFileSave) { scriptType.fileWatcherThread = new KubeJSFileWatcherThread(scriptType, watchingFiles.toArray(new ScriptFile[0]), this::fullReload); scriptType.fileWatcherThread.start(); } + + var broadcast = new JsonObject(); + broadcast.addProperty("type", scriptType.name); + broadcast.addProperty("total", t); + broadcast.addProperty("successful", i); + broadcast.addProperty("errors", scriptType.console.errors.size()); + broadcast.addProperty("warnings", scriptType.console.warnings.size()); + broadcast.addProperty("time", ms); + KubeJSWeb.UPDATES.broadcast("scripts_reloaded", broadcast); } public void loadAdditional() { diff --git a/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPContext.java b/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPContext.java index 66ab34117..dcaee7821 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPContext.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KJSHTTPContext.java @@ -1,8 +1,16 @@ 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.mods.kubejs.util.Cast; import dev.latvian.mods.kubejs.util.RegistryAccessContainer; import dev.latvian.mods.kubejs.web.http.HTTPContext; 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; @@ -28,4 +36,26 @@ public ResourceLocation id(String ns, String path) { 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(); + } } 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 6619c9989..1a6768111 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/KubeJSLocalWebServer.java @@ -11,7 +11,6 @@ import dev.latvian.mods.kubejs.web.ws.WSHandler; import dev.latvian.mods.kubejs.web.ws.WSSessionFactory; import dev.latvian.mods.rhino.util.HideFromJS; -import net.minecraft.client.Minecraft; import net.neoforged.fml.ModList; import org.jetbrains.annotations.Nullable; @@ -31,7 +30,7 @@ public static KubeJSLocalWebServer instance() { public static void start() { if (instance == null) { try { - var address = Inet4Address.getByName(WebServerProperties.get().isPublic ? "0.0.0.0" : "127.0.0.1"); + var address = Inet4Address.getByName(WebServerProperties.get().publicAddress.isEmpty() ? "127.0.0.1" : "0.0.0.0"); var http = new HTTPServer<>(KJSHTTPContext::new); // var ws = new WSServer(address, ClientProperties.get().localServerWsPort); @@ -61,12 +60,12 @@ public void http(HTTPMethod method, String path, HTTPHandler han @Override public WSHandler ws(String path, WSSessionFactory factory) { - return null; + return WSHandler.EMPTY; } private static HTTPResponse homepage(HTTPContext ctx) { var list = new ArrayList(); - list.add("KubeJS Local Web Server [" + Minecraft.getInstance().getGameProfile().getName() + ", " + Minecraft.getInstance().kjs$getTitle() + "]"); + list.add("KubeJS Local Web Server [" + KubeJS.PROXY.getWebServerWindowTitle() + "]"); list.add(""); list.add("Loaded Plugins:"); diff --git a/src/main/java/dev/latvian/mods/kubejs/web/WebServerProperties.java b/src/main/java/dev/latvian/mods/kubejs/web/WebServerProperties.java index 575e3b935..65a237847 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/WebServerProperties.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/WebServerProperties.java @@ -20,18 +20,16 @@ public static void reload() { public boolean enabled; public int port; - public boolean isPublic; public String publicAddress; private WebServerProperties() { - super(KubeJSPaths.CLIENT_PROPERTIES, "KubeJS Web Server Properties"); + super(KubeJSPaths.WEB_SERVER_PROPERTIES, "KubeJS Web Server Properties"); } @Override protected void load() { enabled = get("enabled", true); port = get("port", 61423); - isPublic = get("public", false); publicAddress = get("public_address", ""); } } \ No newline at end of file diff --git a/src/main/java/dev/latvian/mods/kubejs/web/WebServerRegistry.java b/src/main/java/dev/latvian/mods/kubejs/web/WebServerRegistry.java index 85f558a92..73f0fdb96 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/WebServerRegistry.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/WebServerRegistry.java @@ -5,6 +5,7 @@ import dev.latvian.mods.kubejs.web.http.HTTPMethod; import dev.latvian.mods.kubejs.web.http.HTTPResponse; import dev.latvian.mods.kubejs.web.ws.WSHandler; +import dev.latvian.mods.kubejs.web.ws.WSSession; import dev.latvian.mods.kubejs.web.ws.WSSessionFactory; import java.util.function.Consumer; @@ -14,6 +15,10 @@ public interface WebServerRegistry { WSHandler ws(String path, WSSessionFactory factory); + default WSHandler ws(String path) { + return ws(path, WSSession::new); + } + default void get(String path, HTTPHandler handler) { http(HTTPMethod.GET, path, handler); } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPContext.java b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPContext.java index ed4f01fd7..9d82a4003 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPContext.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPContext.java @@ -13,6 +13,7 @@ public class HTTPContext { private String[] path = new String[0]; private Map variables = Map.of(); private Map query = Map.of(); + private final Map headers = Map.of(); private Supplier body = NO_BODY; public void setPath(String[] path) { @@ -61,6 +62,10 @@ public Map query() { return query; } + public String header(String name) { + return ""; + } + public String[] path() { return path; } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponse.java b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponse.java index 5e5232557..efdd11cbe 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponse.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponse.java @@ -1,7 +1,6 @@ package dev.latvian.mods.kubejs.web.http; import com.sun.net.httpserver.HttpExchange; -import org.jetbrains.annotations.Nullable; public interface HTTPResponse { HTTPResponse OK = SimpleHTTPResponse.text(200, "OK"); @@ -9,31 +8,9 @@ public interface HTTPResponse { HTTPResponse WIP = SimpleHTTPResponse.text(404, "WIP"); HTTPResponse NOT_FOUND = SimpleHTTPResponse.text(404, "Not Found"); - int status(); + void respond(HttpExchange exchange) throws Exception; - default String contentType() { - return ""; - } - - @Nullable - default byte[] body() { - return null; - } - - default void respond(HttpExchange exchange) throws Exception { - var bytes = body(); - var contentType = contentType(); - - if (!contentType.isEmpty()) { - exchange.getResponseHeaders().set("Content-Type", contentType); - } - - exchange.sendResponseHeaders(status(), bytes == null ? -1L : bytes.length); - - if (bytes != null) { - try (var os = exchange.getResponseBody()) { - os.write(bytes); - } - } + default HTTPResponse withHeader(String header, String value) { + return new HTTPResponseWithHeader(this, header, value); } } diff --git a/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponseWithHeader.java b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponseWithHeader.java new file mode 100644 index 000000000..6f099433b --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/http/HTTPResponseWithHeader.java @@ -0,0 +1,11 @@ +package dev.latvian.mods.kubejs.web.http; + +import com.sun.net.httpserver.HttpExchange; + +public record HTTPResponseWithHeader(HTTPResponse original, String header, String value) implements HTTPResponse { + @Override + public void respond(HttpExchange exchange) throws Exception { + exchange.getResponseHeaders().set(header, value); + original.respond(exchange); + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/web/http/SimpleHTTPResponse.java b/src/main/java/dev/latvian/mods/kubejs/web/http/SimpleHTTPResponse.java index c0323d2a7..18d871951 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/http/SimpleHTTPResponse.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/http/SimpleHTTPResponse.java @@ -1,6 +1,7 @@ package dev.latvian.mods.kubejs.web.http; import com.google.gson.JsonElement; +import com.sun.net.httpserver.HttpExchange; import dev.latvian.mods.kubejs.util.JsonUtils; import org.jetbrains.annotations.Nullable; @@ -23,4 +24,22 @@ public static SimpleHTTPResponse json(int status, JsonElement json) { public static SimpleHTTPResponse lazyJson(Supplier json) { return json(200, json.get()); } + + @Override + public void respond(HttpExchange exchange) throws Exception { + var bytes = body(); + var contentType = contentType(); + + if (!contentType.isEmpty()) { + exchange.getResponseHeaders().set("Content-Type", contentType); + } + + exchange.sendResponseHeaders(status(), bytes == null ? -1L : bytes.length); + + if (bytes != null) { + try (var os = exchange.getResponseBody()) { + os.write(bytes); + } + } + } } diff --git a/src/main/java/dev/latvian/mods/kubejs/client/web/ConsoleWSSession.java b/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java similarity index 92% rename from src/main/java/dev/latvian/mods/kubejs/client/web/ConsoleWSSession.java rename to src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java index 296888041..43e5168e8 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/web/ConsoleWSSession.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/ConsoleWSSession.java @@ -1,4 +1,4 @@ -package dev.latvian.mods.kubejs.client.web; +package dev.latvian.mods.kubejs.web.local; import com.google.gson.JsonElement; import dev.latvian.mods.kubejs.script.ConsoleJS; diff --git a/src/main/java/dev/latvian/mods/kubejs/client/web/KubeJSWeb.java b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java similarity index 78% rename from src/main/java/dev/latvian/mods/kubejs/client/web/KubeJSWeb.java rename to src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java index 2bfe0f93d..80907b0e9 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/web/KubeJSWeb.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/KubeJSWeb.java @@ -1,4 +1,4 @@ -package dev.latvian.mods.kubejs.client.web; +package dev.latvian.mods.kubejs.web.local; import com.google.gson.JsonArray; import dev.latvian.mods.kubejs.KubeJS; @@ -7,6 +7,7 @@ import dev.latvian.mods.kubejs.web.WebServerRegistry; import dev.latvian.mods.kubejs.web.http.HTTPResponse; import dev.latvian.mods.kubejs.web.http.SimpleHTTPResponse; +import dev.latvian.mods.kubejs.web.ws.WSHandler; import net.minecraft.client.Minecraft; import net.minecraft.core.Holder; import net.minecraft.resources.ResourceKey; @@ -15,22 +16,28 @@ import java.util.Optional; public class KubeJSWeb { - public static void register(WebServerRegistry registry) { - for (var s : ScriptType.VALUES) { - var path = "api/console/" + s.name; + public static WSHandler UPDATES = WSHandler.EMPTY; - s.console.wsBroadcaster = registry.ws(path + "/stream", () -> new ConsoleWSSession(s.console)); + public static void addScriptTypeEndpoints(WebServerRegistry registry, ScriptType s) { + var path = "api/console/" + s.name; - registry.acceptPostString(path + "/info", s.console::info); - registry.acceptPostString(path + "/warn", s.console::warn); - registry.acceptPostString(path + "/error", s.console::error); - registry.get(path + "/errors", s.console::getErrorsResponse); - registry.get(path + "/warnings", s.console::getWarningsResponse); - } + s.console.wsBroadcaster = registry.ws(path + "/stream", () -> new ConsoleWSSession(s.console)); + + registry.acceptPostString(path + "/info", s.console::info); + registry.acceptPostString(path + "/warn", s.console::warn); + registry.acceptPostString(path + "/error", s.console::error); + registry.get(path + "/errors", s.console::getErrorsResponse); + registry.get(path + "/warnings", s.console::getWarningsResponse); + } + + public static void register(WebServerRegistry registry) { + UPDATES = registry.ws("updates"); + + addScriptTypeEndpoints(registry, ScriptType.STARTUP); + addScriptTypeEndpoints(registry, ScriptType.SERVER); registry.acceptPostTask("api/reload/startup", KubeJS.getStartupScriptManager()::reload); registry.acceptPostTask("api/reload/server", KubeJSWeb::reloadInternalServer); - registry.acceptPostTask("api/reload/client", KubeJS.getClientScriptManager()::reload); 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 @@ -38,13 +45,6 @@ public static void register(WebServerRegistry 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("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 void reloadInternalServer() { diff --git a/src/main/java/dev/latvian/mods/kubejs/client/web/ImageGenerator.java b/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java similarity index 72% rename from src/main/java/dev/latvian/mods/kubejs/client/web/ImageGenerator.java rename to src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java index 3df8d9752..b150af6e5 100644 --- a/src/main/java/dev/latvian/mods/kubejs/client/web/ImageGenerator.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/ImageGenerator.java @@ -1,4 +1,4 @@ -package dev.latvian.mods.kubejs.client.web; +package dev.latvian.mods.kubejs.web.local.client; import com.mojang.blaze3d.pipeline.TextureTarget; import com.mojang.blaze3d.platform.NativeImage; @@ -8,7 +8,6 @@ import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexSorting; -import com.mojang.brigadier.StringReader; import dev.latvian.mods.kubejs.KubeJSPaths; import dev.latvian.mods.kubejs.bindings.BlockWrapper; import dev.latvian.mods.kubejs.bindings.UUIDWrapper; @@ -44,24 +43,36 @@ private record RenderImage(Minecraft mc, GuiGraphics graphics, int size) { public static final Int2ObjectMap FB_CACHE = new Int2ObjectArrayMap<>(); - private static HTTPResponse renderCanvas(KJSHTTPContext ctx, int canvasSize, String cacheId, Consumer render) { + private static HTTPResponse renderCanvas(KJSHTTPContext ctx, int canvasSize, StringBuilder cacheId, Consumer render) { int size = Integer.parseInt(ctx.variables().get("size")); if (size < 1 || size > 1024) { return SimpleHTTPResponse.text(400, "Invalid size, must be [1, 1024]"); } - var bytes = ctx.supplyInRenderThread(() -> { - var cacheUUID = UUIDWrapper.toString(UUID.nameUUIDFromBytes((cacheId + canvasSize).getBytes(StandardCharsets.UTF_8))) + ".png"; - var cachePath = KubeJSPaths.dir(KubeJSPaths.LOCAL_CACHE.resolve("web/img/" + cacheUUID.substring(0, 2))).resolve(cacheUUID); + if (!cacheId.isEmpty()) { + cacheId.append(size); + } + + if (cacheId.isEmpty() && ctx.header("Accept").equals("text/plain")) { + return SimpleHTTPResponse.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"); - if (Files.exists(cachePath)) { - try { - return Files.readAllBytes(cachePath); - } catch (IOException ignore) { + if (cachePath != null && Files.exists(cachePath)) { + try { + if (ctx.header("Accept").equals("text/plain")) { + return SimpleHTTPResponse.text(200, cacheUUID); } + + return new SimpleHTTPResponse(200, Files.readAllBytes(cachePath), "image/png").withHeader("X-KubeJS-Cache-Key", cacheUUID); + } catch (IOException ignore) { } + } + var bytes = ctx.supplyInRenderThread(() -> { var target = FB_CACHE.get(size); if (target == null) { @@ -92,14 +103,7 @@ private static HTTPResponse renderCanvas(KJSHTTPContext ctx, int canvasSize, Str try (var image = new NativeImage(size, size, false)) { image.downloadTexture(0, false); image.flipY(); - var result = image.asByteArray(); - - try { - Files.write(cachePath, result); - } catch (Exception ignore) { - } - - return result; + return image.asByteArray(); } catch (Exception ex) { ex.printStackTrace(); return null; @@ -112,18 +116,37 @@ private static HTTPResponse renderCanvas(KJSHTTPContext ctx, int canvasSize, Str } }); - return new SimpleHTTPResponse(200, bytes, "image/png"); + if (cachePath != null) { + try { + Files.write(cachePath, bytes); + } catch (Exception ignore) { + } + } + + if (ctx.header("Accept").equals("text/plain")) { + if (cachePath == null) { + return SimpleHTTPResponse.NOT_FOUND; + } + + return SimpleHTTPResponse.text(200, cacheUUID); + } + + return new SimpleHTTPResponse(200, bytes, "image/png").withHeader("X-KubeJS-Cache-Key", cacheUUID); } public static HTTPResponse item(KJSHTTPContext ctx) throws Exception { var stack = BuiltInRegistries.ITEM.get(ctx.id()).getDefaultInstance(); - stack.applyComponents(DataComponentWrapper.readPatch(ctx.registries().nbt(), new StringReader(ctx.query().getOrDefault("components", "")))); + stack.applyComponents(ctx.queryAsPatch(ctx.registries().nbt())); if (stack.isEmpty()) { return HTTPResponse.NOT_FOUND; } - return renderCanvas(ctx, 16, stack.kjs$getId() + stack.getComponents(), render -> { + var sb = new StringBuilder(); + sb.append(stack.kjs$getId()); + DataComponentWrapper.writeVisualComponentsForCache(sb, ctx.registries().nbt(), stack.getComponents()); + + return renderCanvas(ctx, 16, sb, render -> { render.graphics.renderFakeItem(stack, 0, 0, 0); render.graphics.renderItemDecorations(render.mc.font, stack, 0, 0); }); @@ -136,14 +159,22 @@ public static HTTPResponse block(KJSHTTPContext ctx) throws Exception { return HTTPResponse.NOT_FOUND; } - return renderCanvas(ctx, 16, "", render -> { + var sb = new StringBuilder(); + sb.append(state.kjs$getId()); + + for (var entry : ctx.query().entrySet()) { + sb.append(entry.getKey()); + sb.append(entry.getValue()); + } + + return renderCanvas(ctx, 16, sb, render -> { render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); }); } public static HTTPResponse fluid(KJSHTTPContext ctx) throws Exception { var stack = new FluidStack(BuiltInRegistries.FLUID.get(ctx.id()), FluidType.BUCKET_VOLUME); - stack.applyComponents(DataComponentWrapper.readPatch(ctx.registries().nbt(), new StringReader(ctx.query().getOrDefault("components", "")))); + stack.applyComponents(ctx.queryAsPatch(ctx.registries().nbt())); if (stack.isEmpty()) { return HTTPResponse.NOT_FOUND; @@ -157,7 +188,11 @@ public static HTTPResponse fluid(KJSHTTPContext ctx) throws Exception { int g = (tint >> 8) & 0xFF; int b = tint & 0xFF; - return renderCanvas(ctx, 16, "", render -> { + var sb = new StringBuilder(); + sb.append(stack.getFluid().kjs$getId()); + DataComponentWrapper.writeVisualComponentsForCache(sb, ctx.registries().nbt(), stack.getComponents()); + + return renderCanvas(ctx, 16, sb, render -> { var s = render.mc.getTextureAtlas(TextureAtlas.LOCATION_BLOCKS).apply(still); RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_BLOCKS); RenderSystem.setShader(GameRenderer::getPositionTexColorShader); @@ -174,7 +209,7 @@ public static HTTPResponse fluid(KJSHTTPContext ctx) throws Exception { public static HTTPResponse itemTag(KJSHTTPContext ctx) throws Exception { var tagKey = ItemTags.create(ctx.id()); - return renderCanvas(ctx, 16, "", render -> { + return renderCanvas(ctx, 16, new StringBuilder(), render -> { // render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); render.graphics.renderFakeItem(Items.NAME_TAG.getDefaultInstance(), 0, 0, 0); }); @@ -183,7 +218,7 @@ public static HTTPResponse itemTag(KJSHTTPContext ctx) throws Exception { public static HTTPResponse blockTag(KJSHTTPContext ctx) throws Exception { var tagKey = BlockTags.create(ctx.id()); - return renderCanvas(ctx, 16, "", render -> { + return renderCanvas(ctx, 16, new StringBuilder(), render -> { // render.graphics.fill(0, 0, 16, 16, 0xFFFF00FF); render.graphics.renderFakeItem(Items.NAME_TAG.getDefaultInstance(), 0, 0, 0); }); @@ -192,7 +227,7 @@ public static HTTPResponse blockTag(KJSHTTPContext ctx) throws Exception { public static HTTPResponse fluidTag(KJSHTTPContext ctx) throws Exception { var tagKey = FluidTags.create(ctx.id()); - return renderCanvas(ctx, 16, "", render -> { + return renderCanvas(ctx, 16, 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 new file mode 100644 index 000000000..4fe9eca9a --- /dev/null +++ b/src/main/java/dev/latvian/mods/kubejs/web/local/client/KubeJSClientWeb.java @@ -0,0 +1,62 @@ +package dev.latvian.mods.kubejs.web.local.client; + +import com.google.gson.JsonArray; +import dev.latvian.mods.kubejs.KubeJS; +import dev.latvian.mods.kubejs.script.ScriptType; +import dev.latvian.mods.kubejs.web.KJSHTTPContext; +import dev.latvian.mods.kubejs.web.WebServerRegistry; +import dev.latvian.mods.kubejs.web.http.HTTPResponse; +import dev.latvian.mods.kubejs.web.http.SimpleHTTPResponse; +import dev.latvian.mods.kubejs.web.local.KubeJSWeb; +import net.minecraft.client.Minecraft; + +public class KubeJSClientWeb { + public static void register(WebServerRegistry registry) { + KubeJSWeb.addScriptTypeEndpoints(registry, ScriptType.CLIENT); + + registry.acceptPostTask("api/reload/client", KubeJS.getClientScriptManager()::reload); + + registry.get("assets", KubeJSClientWeb::getAssets); + registry.get("assets/{namespace}/", KubeJSClientWeb::getAsset); + + 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 getAssets(KJSHTTPContext ctx) { + return SimpleHTTPResponse.lazyJson(() -> { + var json = new JsonArray(); + + for (var id : Minecraft.getInstance().getResourceManager().listPacks().toList()) { + json.add(id.toString()); + } + + return json; + }); + } + + private static HTTPResponse getAsset(KJSHTTPContext ctx) throws Exception { + var id = ctx.id(); + var asset = Minecraft.getInstance().getResourceManager().getResource(id); + + if (asset.isEmpty()) { + return HTTPResponse.NOT_FOUND; + } + + try (var in = asset.get().open()) { + if (id.getPath().endsWith(".png")) { + return new SimpleHTTPResponse(200, in.readAllBytes(), "image/png"); + } else if (id.getPath().endsWith(".json") || id.getPath().endsWith(".mcmeta")) { + return new SimpleHTTPResponse(200, in.readAllBytes(), "application/json; charset=utf-8"); + } else if (id.getPath().endsWith(".ogg")) { + return new SimpleHTTPResponse(200, in.readAllBytes(), "audio/ogg"); + } else { + return new SimpleHTTPResponse(200, in.readAllBytes(), "text/plain"); + } + } + } +} diff --git a/src/main/java/dev/latvian/mods/kubejs/web/ws/WSHandler.java b/src/main/java/dev/latvian/mods/kubejs/web/ws/WSHandler.java index 2237a489d..2d162e901 100644 --- a/src/main/java/dev/latvian/mods/kubejs/web/ws/WSHandler.java +++ b/src/main/java/dev/latvian/mods/kubejs/web/ws/WSHandler.java @@ -2,25 +2,54 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import org.jetbrains.annotations.Nullable; import java.util.function.Supplier; public interface WSHandler { + WSHandler EMPTY = new WSHandler() { + @Override + public void broadcast(String message) { + } + + @Override + public void broadcast(byte[] bytes) { + } + + @Override + public void broadcast(String event, Supplier payload) { + } + + @Override + public void broadcast(String event, String payload) { + } + + @Override + public void broadcast(String event, @Nullable JsonElement payload) { + } + }; + void broadcast(String message); void broadcast(byte[] bytes); default void broadcast(String event, Supplier payload) { - var json = new JsonObject(); - json.addProperty("event", event); - json.add("payload", payload.get()); - broadcast(json.toString()); + broadcast(event, payload.get()); + } + + default void broadcast(String event, String payload) { + broadcast(event, new JsonPrimitive(payload)); } - default void broadcast(String event, JsonElement payload) { + default void broadcast(String event, @Nullable JsonElement payload) { var json = new JsonObject(); json.addProperty("event", event); - json.add("payload", payload); + + if (payload != null && !payload.isJsonNull()) { + json.add("payload", payload); + } + broadcast(json.toString()); } }