diff --git a/.github/workflows/javadoc-releases.yml b/.github/workflows/javadoc-releases.yml new file mode 100644 index 0000000..97fa29e --- /dev/null +++ b/.github/workflows/javadoc-releases.yml @@ -0,0 +1,19 @@ +name: Deploy Javadoc + +on: + release: + types: + - published + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: 🚀 Deploy JavaDoc Release + uses: MathieuSoysal/Javadoc-publisher.yml@v2.4.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + javadoc-branch: javadoc + java-version: 17 + target-folder: docs/${{ github.event.release.tag_name }} + project: gradle diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml new file mode 100644 index 0000000..3b44c85 --- /dev/null +++ b/.github/workflows/javadoc.yml @@ -0,0 +1,20 @@ +name: Deploy Javadoc + +on: + push: + branches: + - main + - dev + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: 🚀 Deploy JavaDoc + uses: MathieuSoysal/Javadoc-publisher.yml@v2.4.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + javadoc-branch: javadoc + java-version: 17 + target-folder: docs/latest + project: gradle diff --git a/build.gradle b/build.gradle index e2e9fb9..d36d5dd 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ //file:noinspection GroovyAccessibility plugins { id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "0.12.0-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.3-SNAPSHOT" apply false } repositories { gradlePluginPortal() @@ -33,6 +33,8 @@ subprojects { officialMojangMappings() parchment("org.parchmentmc.data:parchment-1.19.2:2022.08.10@zip") } + + compileOnly 'com.google.errorprone:error_prone_annotations:2.16' } } diff --git a/common/build.gradle b/common/build.gradle index f756834..70058ec 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,6 +20,19 @@ dependencies { modApi "com.github.Ultreon.ultreonlib:ultreon-lib:$ultreonlib_version" + api "com.github.Ultreon:ultreon-data:$ultreon_data_version" + + api "com.github.Ultreon.corelibs:corelibs-collections-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-commons-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-crash-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-datetime-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-events-v1:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-functions-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-registries-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-resources-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-text-v0:$corelibs_version" + api "com.github.Ultreon.corelibs:corelibs-translations-v0:$corelibs_version" + modImplementation "fuzs.forgeconfigapiport:forgeconfigapiport-fabric:$forge_config_api_port_version" modCompileOnly 'com.electronwill.night-config:core:3.6.3' modCompileOnly 'com.electronwill.night-config:toml:3.6.3' @@ -54,5 +67,6 @@ publishing { // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { // Add repositories to publish to here. + mavenLocal() } } \ No newline at end of file diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/AdvancedDebug.java b/common/src/main/java/com/ultreon/mods/advanceddebug/AdvancedDebug.java index f1b3b3c..5849381 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/AdvancedDebug.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/AdvancedDebug.java @@ -9,7 +9,9 @@ import com.ultreon.mods.advanceddebug.client.registry.FormatterRegistry; import com.ultreon.mods.advanceddebug.extension.ExtensionLoader; import com.ultreon.mods.advanceddebug.init.ModDebugPages; +import com.ultreon.mods.advanceddebug.init.ModInspectionInit; import com.ultreon.mods.advanceddebug.init.ModOverlays; +import com.ultreon.mods.advanceddebug.inspect.InspectionRoot; import com.ultreon.mods.advanceddebug.util.TargetUtils; import dev.architectury.event.events.client.ClientLifecycleEvent; import dev.architectury.event.events.client.ClientTickEvent; @@ -34,6 +36,8 @@ public class AdvancedDebug implements IAdvancedDebug { private static final ExtensionLoader loader = ExtensionLoader.get(); + public final InspectionRoot inspections = new InspectionRoot<>(Minecraft.getInstance()); + public static AdvancedDebug getInstance() { return instance; } @@ -50,6 +54,8 @@ private static void tick(Minecraft minecraft) { public void init() { ModOverlays.registerAll(); + ModInspectionInit.registerAutoFillers(); + ClientLifecycleEvent.CLIENT_STARTED.register(this::setup); ClientTickEvent.CLIENT_POST.register(AdvancedDebug::tick); @@ -129,5 +135,12 @@ private void setup(final Minecraft client) { LOGGER.debug("Client side setup done!"); ModDebugPages.init(); + + this.inspections.createNode("player", value -> value.player); + this.inspections.createNode("level", value -> value.level); + this.inspections.createNode("screen", value -> value.screen); + this.inspections.createNode("window", Minecraft::getWindow); + this.inspections.createNode("singleplayerServer", Minecraft::getSingleplayerServer); + this.inspections.createNode("currentServer", Minecraft::getCurrentServer); } } diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/menu/IDebugGui.java b/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/menu/IDebugGui.java index 1f2d4c4..f0174c0 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/menu/IDebugGui.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/menu/IDebugGui.java @@ -3,8 +3,7 @@ import com.ultreon.libs.commons.v0.Identifier; import com.ultreon.mods.advanceddebug.api.client.formatter.IFormatterContext; import com.ultreon.mods.advanceddebug.api.common.IFormatter; - -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public interface IDebugGui extends IFormatter { T registerPage(Identifier id, T page); diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/registry/IFormatterRegistry.java b/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/registry/IFormatterRegistry.java index de164c0..31582aa 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/registry/IFormatterRegistry.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/api/client/registry/IFormatterRegistry.java @@ -1,8 +1,7 @@ package com.ultreon.mods.advanceddebug.api.client.registry; import com.ultreon.mods.advanceddebug.api.client.menu.Formatter; - -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public interface IFormatterRegistry { Formatter register(Formatter formatter); diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/api/extension/Extension.java b/common/src/main/java/com/ultreon/mods/advanceddebug/api/extension/Extension.java index 6eca31b..88ddddd 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/api/extension/Extension.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/api/extension/Extension.java @@ -19,7 +19,9 @@ default void initFormatters(IFormatterRegistry formatterRegistry) { } - void handleImGuiMenuBar(); + default void handleImGuiMenuBar() { + + } default void handleEntity(Entity entity) { diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugGui.java b/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugGui.java index 01e3d37..97976ba 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugGui.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugGui.java @@ -1,8 +1,10 @@ package com.ultreon.mods.advanceddebug.client.menu; import com.google.common.collect.Lists; +import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.systems.RenderSystem; import com.ultreon.libs.commons.v0.Identifier; +import com.ultreon.libs.commons.v0.tuple.Pair; import com.ultreon.libs.commons.v0.util.ClassUtils; import com.ultreon.mods.advanceddebug.AdvancedDebug; import com.ultreon.mods.advanceddebug.IllegalThreadError; @@ -19,6 +21,8 @@ import com.ultreon.mods.advanceddebug.client.registry.FormatterRegistry; import com.ultreon.mods.advanceddebug.extension.ExtensionLoader; import com.ultreon.mods.advanceddebug.init.ModDebugPages; +import com.ultreon.mods.advanceddebug.inspect.InspectionNode; +import com.ultreon.mods.advanceddebug.inspect.InspectionRoot; import com.ultreon.mods.advanceddebug.mixin.common.ImageButtonAccessor; import com.ultreon.mods.advanceddebug.registry.ModPreRegistries; import com.ultreon.mods.advanceddebug.text.ComponentBuilder; @@ -35,13 +39,13 @@ import it.unimi.dsi.fastutil.longs.LongSet; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.ChatFormatting; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.client.Minecraft; import net.minecraft.client.MouseHandler; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Renderable; import net.minecraft.client.gui.components.*; import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.components.events.GuiEventListener; @@ -58,7 +62,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.damagesource.CombatEntry; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectUtil; import net.minecraft.world.entity.Entity; @@ -86,14 +89,16 @@ import net.minecraft.world.scores.Team; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; import org.slf4j.Marker; import org.slf4j.MarkerFactory; -import javax.annotation.Nullable; import java.awt.*; import java.util.List; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import static net.minecraft.ChatFormatting.*; @@ -121,6 +126,7 @@ public void format(Object obj, IFormatterContext context) { private static final Marker MARKER = MarkerFactory.getMarker("DebugGui"); + public static final ImBoolean SHOW_OBJECT_INSPECTION = new ImBoolean(false); public static final ImBoolean SHOW_PLAYER_INFO = new ImBoolean(false); public static final ImBoolean SHOW_CLIENT_LEVEL_INFO = new ImBoolean(false); public static final ImBoolean SHOW_SERVER_LEVEL_INFO = new ImBoolean(false); @@ -146,6 +152,9 @@ public void format(Object obj, IFormatterContext context) { private int height; private final ReentrantLock lock = new ReentrantLock(true); private boolean enabled = true; + private final boolean[] pressed = new boolean[GLFW.GLFW_KEY_LAST + 1]; + private String inspectCurrentPath = "/"; + private String inspectIdxInput = ""; private DebugGui() { ClassUtils.checkCallerClassEquals(DebugGui.class); @@ -163,9 +172,7 @@ private DebugGui() { } private void enable() { - RenderSystem.recordRenderCall(() -> { - enabled = true; - }); + RenderSystem.recordRenderCall(() -> enabled = true); } /** @@ -241,6 +248,10 @@ protected void drawLine(@NotNull GuiGraphics gfx, Component text, int x, int y) } }; + long window = mc.getWindow().getWindow(); + if (SHOW_OBJECT_INSPECTION.get()) { + this.renderObjectInspection(gfx, window, context, AdvancedDebug.getInstance().inspections); + } if (debugPage != null) { gfx.pose().pushPose(); { @@ -274,7 +285,88 @@ protected void drawLine(@NotNull GuiGraphics gfx, Component text, int x, int y) lock.unlock(); } - public synchronized static void renderImGui(ImGuiImplGlfw glfw, ImGuiImplGl3 gl3) { + private void renderObjectInspection(GuiGraphics gfx, long window, DebugRenderContext ctx, InspectionRoot inspections) { + + String path = this.inspectCurrentPath; + + Comparator> comparator = Comparator.comparing(InspectionNode::getName); + + ctx.left(gfx, Component.literal(this.inspectIdxInput == null ? "" : this.inspectIdxInput).withStyle(ChatFormatting.WHITE)); + + ctx.left(gfx, Component.literal(path).withStyle(s -> s.withColor(BLUE).withBold(true).withUnderlined(true))); + ctx.left(); + + if (renderInspections(gfx, ctx, inspections, path, comparator)) return; + + if (this.isKeyJustPressed(GLFW.GLFW_KEY_KP_ENTER)) { + String input = this.inspectIdxInput; + try { + int idx = Integer.parseInt(input); + + @Nullable InspectionNode node = inspections.getNode(path); + if (node == null) { + this.inspectCurrentPath = "/"; + return; + } + List> nodes = node.getNodes().values().stream().sorted(comparator).toList(); + + if (nodes.isEmpty()) return; + + if (idx >= 0 && idx < nodes.size()) { + path += nodes.get(idx).getName() + "/"; + this.inspectCurrentPath = path; + } + this.inspectIdxInput = ""; + } catch (NumberFormatException ignored) { + this.inspectIdxInput = ""; + } + } else if (this.isKeyJustPressed(GLFW.GLFW_KEY_BACKSPACE)) { + if (this.inspectCurrentPath.equals("/")) { + return; + } + + if (path.endsWith("/")) path = path.substring(0, path.length() - 1); + this.inspectCurrentPath = path.substring(0, path.lastIndexOf("/")) + "/"; + } else if (this.isKeyJustPressed(GLFW.GLFW_KEY_KP_DECIMAL)) { + this.inspectIdxInput = ""; + } else for (int num = 0; num < 10; num++) { + int key = GLFW.GLFW_KEY_KP_0 + num; + if (this.isKeyJustPressed(key)) { + this.inspectIdxInput += String.valueOf(num); + break; + } + } + } + + private boolean renderInspections(GuiGraphics gfx, DebugRenderContext ctx, InspectionRoot inspections, String path, Comparator> comparator) { + @Nullable InspectionNode node = inspections.getNode(path); + if (node == null) { + this.inspectCurrentPath = "/"; + return true; + } + + List> nodes = node.getNodes().values().stream().sorted(comparator).toList(); + int i = 0; + for (int nodesSize = nodes.size(); i < nodesSize; i++) { + InspectionNode curNode = nodes.get(i); + ctx.left(gfx, Component.literal("[" + i + "]: ").withStyle(s -> s.withColor(GOLD).withBold(true)).append(Component.literal(curNode.getName()).withStyle(s -> s.withColor(WHITE).withBold(false)))); + } + + List> elements = node.getElements().entrySet().stream().map(t -> new Pair<>(t.getKey(), t.getValue().get())).sorted(Comparator.comparing(Pair::getFirst)).toList(); + for (Pair element : elements) { + ctx.left(gfx, Component.literal(element.getFirst() + " = ").withStyle(s -> s.withColor(GRAY).withBold(false)).append(Component.literal(element.getSecond()).withStyle(s -> s.withColor(WHITE).withItalic(true)))); + } + return false; + } + + private boolean isKeyJustPressed(int key) { + var pressed = GLFW.glfwGetKey(this.minecraft.getWindow().getWindow(), key) == GLFW.GLFW_PRESS; + boolean wasPressed = this.pressed[key]; + this.pressed[key] = pressed; + return pressed && !wasPressed; + } + + public static synchronized void renderImGui(ImGuiImplGlfw glfw, ImGuiImplGl3 gl3) { if (!RenderSystem.isOnRenderThread()) throw new IllegalThreadError(); if (!get().enabled) return; if (renderingImGui) return; @@ -311,6 +403,7 @@ public synchronized static void renderImGui(ImGuiImplGlfw glfw, ImGuiImplGl3 gl3 ImGui.menuItem("Show Server Level Info", null, SHOW_SERVER_LEVEL_INFO); ImGui.menuItem("Show Window Info", null, SHOW_WINDOW_INFO); ImGui.menuItem("Show Screen Info", null, SHOW_SCREEN_INFO); + ImGui.menuItem("Show Object Inspection", null, SHOW_OBJECT_INSPECTION); ImGui.endMenu(); } ExtensionLoader.invoke(Extension::handleImGuiMenuBar); @@ -448,7 +541,7 @@ public static void showScreenInfo(Screen screen) { } } - public static void showWindowInfo(com.mojang.blaze3d.platform.Window window) { + public static void showWindowInfo(Window window) { ImGuiEx.text("Size:", () -> window.getWidth() + " × " + window.getHeight()); ImGuiEx.text("Scaled Size:", () -> window.getGuiScaledWidth() + " × " + window.getGuiScaledHeight()); ImGuiEx.text("GUI Scale:", window::getGuiScale); @@ -564,18 +657,13 @@ public static void showLevelInfo(Level level, float frameTime) { ImGuiEx.bool("Thundering:", level::isThundering); } - @org.jetbrains.annotations.Nullable - private static ServerLevel getServerLevel(MinecraftServer server) { + @Nullable + public static ServerLevel getServerLevel(MinecraftServer server) { ClientLevel clientLevel = Minecraft.getInstance().level; return clientLevel == null ? null : server.getLevel(clientLevel.dimension()); } - @ApiStatus.Internal - public static void dispose() { - - } - - private static void showSelectedBlock(SelectedBlock block) { + public static void showSelectedBlock(SelectedBlock block) { Level level = block.getLevel(); BlockPos pos = block.getPos(); BlockEntity blockEntity = block.getBlockEntity(); @@ -596,54 +684,33 @@ private static void showSelectedBlock(SelectedBlock block) { } catch (Exception e) { return Blocks.VOID_AIR.arch$registryName(); } - }, resourceLocation -> { - level.setBlock(pos, BuiltInRegistries.BLOCK.get(resourceLocation).defaultBlockState(), 0b00000011); - }); + }, resourceLocation -> level.setBlock(pos, BuiltInRegistries.BLOCK.get(resourceLocation).defaultBlockState(), 0b00000011)); - if (blockEntity != null) { - if (ImGui.collapsingHeader("Block Entity Info")) { - ImGui.treePush(); - showBlockEntity(blockEntity); - ImGui.treePop(); - } + if (blockEntity != null && ImGui.collapsingHeader("Block Entity Info")) { + ImGui.treePush(); + showBlockEntity(blockEntity); + ImGui.treePop(); } - if (blockState != null) { - if (ImGui.collapsingHeader("Block State Info")) { - ImGui.treePush(); - showBlockState(blockState); - ImGui.treePop(); - } + if (blockState != null && ImGui.collapsingHeader("Block State Info")) { + ImGui.treePush(); + showBlockState(blockState); + ImGui.treePop(); } - if (fluidState != null) { - if (ImGui.collapsingHeader("Fluid State Info")) { - ImGui.treePush(); - showBlockState(blockState); - ImGui.treePop(); - } + if (fluidState != null && ImGui.collapsingHeader("Fluid State Info")) { + ImGui.treePush(); + showBlockState(blockState); + ImGui.treePop(); } } - private static void showBlockState(BlockState blockState) { + public static void showBlockState(BlockState blockState) { ImGuiEx.text("Light Emission:", blockState::getLightEmission); ImGuiEx.text("Render Shape:", () -> blockState.getRenderShape().name()); ImGuiEx.bool("Air:", blockState::isAir); ImGuiEx.bool("Signal Source:", blockState::isSignalSource); ImGuiEx.bool("Randomly Ticking:", blockState::isRandomlyTicking); -// Material material = blockState.getMaterial(); -// if (ImGui.collapsingHeader("Material Info")) { -// ImGui.treePush(); -// ImGuiEx.text("Color:", () -> "#%08x".formatted(material.getColor().col)); -// ImGuiEx.text("Color ID:", () -> material.getColor().id); -// ImGuiEx.text("Push Reaction:", () -> material.getPushReaction().name()); -// ImGuiEx.bool("Flammable:", material::isFlammable); -// ImGuiEx.bool("Liquid:", material::isLiquid); -// ImGuiEx.bool("Replaceable:", material::isReplaceable); -// ImGuiEx.bool("Solid Blocking:", material::isSolidBlocking); -// ImGuiEx.bool("Solid:", material::isSolid); -// ImGui.treePop(); -// } SoundType soundType = blockState.getSoundType(); if (ImGui.collapsingHeader("Sound Type Info")) { ImGui.treePush(); @@ -667,7 +734,7 @@ private static void showBlockState(BlockState blockState) { } @SuppressWarnings("ConstantValue") - private static void showBlockEntity(BlockEntity blockEntity) { + public static void showBlockEntity(BlockEntity blockEntity) { ResourceLocation key = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity.getType()); ImGuiEx.text("Id:", () -> key); @@ -744,47 +811,35 @@ public static void showEntity(Entity entity) { ImGui.treePop(); } } - if (entity instanceof LivingEntity livingEntity) { - if (ImGui.collapsingHeader("Living Info")) { - ImGui.treePush(); - showLivingInfo(livingEntity); - ImGui.treePop(); - } + if (entity instanceof LivingEntity livingEntity && ImGui.collapsingHeader("Living Info")) { + ImGui.treePush(); + showLivingInfo(livingEntity); + ImGui.treePop(); } - if (entity instanceof Mob mob) { - if (ImGui.collapsingHeader("Mob Info")) { - ImGui.treePush(); - showMobInfo(mob); - ImGui.treePop(); - } + if (entity instanceof Mob mob && ImGui.collapsingHeader("Mob Info")) { + ImGui.treePush(); + showMobInfo(mob); + ImGui.treePop(); } - if (entity instanceof AbstractVillager villager) { - if (ImGui.collapsingHeader("Base Villager Info")) { - ImGui.treePush(); - showBaseVillagerInfo(villager); - ImGui.treePop(); - } + if (entity instanceof AbstractVillager villager && ImGui.collapsingHeader("Base Villager Info")) { + ImGui.treePush(); + showBaseVillagerInfo(villager); + ImGui.treePop(); } - if (entity instanceof ItemEntity itemEntity) { - if (ImGui.collapsingHeader("Item Info")) { - ImGui.treePush(); - showItemEntityInfo(itemEntity); - ImGui.treePop(); - } + if (entity instanceof ItemEntity itemEntity && ImGui.collapsingHeader("Item Info")) { + ImGui.treePush(); + showItemEntityInfo(itemEntity); + ImGui.treePop(); } - if (entity instanceof Projectile projectile) { - if (ImGui.collapsingHeader("Projectile Info")) { - ImGui.treePush(); - showProjectileInfo(projectile); - ImGui.treePop(); - } + if (entity instanceof Projectile projectile && ImGui.collapsingHeader("Projectile Info")) { + ImGui.treePush(); + showProjectileInfo(projectile); + ImGui.treePop(); } - if (entity instanceof Player player) { - if (ImGui.collapsingHeader("Player Info")) { - ImGui.treePush(); - showPlayerInfo(player); - ImGui.treePop(); - } + if (entity instanceof Player player && ImGui.collapsingHeader("Player Info")) { + ImGui.treePush(); + showPlayerInfo(player); + ImGui.treePop(); } if (ImGui.collapsingHeader("Extensions")) { ImGui.treePush(); @@ -1228,4 +1283,9 @@ public static boolean isImGuiHovered() { public static boolean isImGuiFocused() { return imGuiFocused; } + + @ApiStatus.Internal + public static void dispose() { + + } } diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugRenderContext.java b/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugRenderContext.java index 23b856d..9659dda 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugRenderContext.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/client/menu/DebugRenderContext.java @@ -5,8 +5,11 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import org.jetbrains.annotations.NotNull; +import java.awt.*; + public abstract class DebugRenderContext implements IDebugRenderContext { private int left; private int top; @@ -26,102 +29,126 @@ public DebugRenderContext(@NotNull GuiGraphics gfx, int width, int height) { } public GuiGraphics getGuiGraphics() { - return guiGraphics; + return this.guiGraphics; } public int getWidth() { - return width; + return this.width; } public int getHeight() { - return height; + return this.height; } @Override public void left(Component text, Object object, Object... objects) { - drawLeft(guiGraphics, text.getString(), left++, object, objects); + this.drawLeft(this.guiGraphics, text.getString(), this.left++, object, objects); } @Override public void left(String text, Object object, Object... objects) { - drawLeft(guiGraphics, text, left++, object, objects); + this.drawLeft(this.guiGraphics, text, this.left++, object, objects); } @Override public void left(Component text) { - drawLeft(guiGraphics, text.getString(), left++); + this.drawLeft(this.guiGraphics, text.getString(), this.left++); } @Override public void left(String text) { - drawLeft(guiGraphics, ChatFormatting.GRAY + "-== " + text + ChatFormatting.GRAY + " ==-", left++); + this.drawLeft(this.guiGraphics, ChatFormatting.GRAY + "-== " + text + ChatFormatting.GRAY + " ==-", this.left++); } @Override public void left() { - left++; + this.left++; } @Override public void right(Component text, Object object, Object... objects) { - drawRight(guiGraphics, text.getString(), right++, object, objects); + this.drawRight(this.guiGraphics, text.getString(), this.right++, object, objects); } @Override public void right(String text, Object object, Object... objects) { - drawRight(guiGraphics, text, right++, object, objects); + this.drawRight(this.guiGraphics, text, this.right++, object, objects); } @Override public void right(Component text) { - drawRight(guiGraphics, text.getString(), right++); + this.drawRight(this.guiGraphics, text.getString(), this.right++); } @Override public void right(String text) { - drawRight(guiGraphics, ChatFormatting.GRAY + "-== " + text + ChatFormatting.GRAY + " ==-", right++); + this.drawRight(this.guiGraphics, ChatFormatting.GRAY + "-== " + text + ChatFormatting.GRAY + " ==-", this.right++); } @Override public void right() { - right++; + this.right++; } @Override public void top(Component text) { - drawTop(guiGraphics, text.getString(), top++); + this.drawTop(this.guiGraphics, text.getString(), this.top++); } @Override public void top(String text) { - drawTop(guiGraphics, text, top++); + this.drawTop(this.guiGraphics, text, this.top++); } @Override public void top() { - top++; + this.top++; } private void drawTop(@NotNull GuiGraphics gfx, String text, int line) { - drawLine(gfx, Component.literal(text), (int) (this.width / 2f - mc.font.width(text) / 2f), VERTICAL_OFFSET + line * (mc.font.lineHeight + 2)); + this.drawLine(gfx, Component.literal(text), (int) (this.width / 2f - this.mc.font.width(text) / 2f), VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); + } + + private void drawTop(@NotNull GuiGraphics gfx, Component text, int line) { + this.drawLine(gfx, text, (int) (this.width / 2f - this.mc.font.width(text) / 2f), VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); } private void drawLeft(@NotNull GuiGraphics gfx, String text, int line, Object obj, Object... objects) { - drawLine(gfx, DebugGui.get().format(text, obj, objects), HORIZONTAL_OFFSET, VERTICAL_OFFSET + line * (mc.font.lineHeight + 2)); + this.drawLine(gfx, DebugGui.get().format(text, obj, objects), HORIZONTAL_OFFSET, VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); } private void drawLeft(@NotNull GuiGraphics gfx, String text, int line) { - drawLine(gfx, Component.literal(text), HORIZONTAL_OFFSET, VERTICAL_OFFSET + line * (mc.font.lineHeight + 2)); + this.drawLine(gfx, Component.literal(text), HORIZONTAL_OFFSET, VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); + } + + private void drawLeft(@NotNull GuiGraphics gfx, Component text, int line) { + this.drawLine(gfx, text, HORIZONTAL_OFFSET, VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); } private void drawRight(@NotNull GuiGraphics gfx, String text, int line, Object obj, Object... objects) { Component format = DebugGui.get().format(text, obj, objects); - drawLine(gfx, format, this.width - HORIZONTAL_OFFSET - mc.font.width(format), VERTICAL_OFFSET + line * (mc.font.lineHeight + 2)); + this.drawLine(gfx, format, this.width - HORIZONTAL_OFFSET - this.mc.font.width(format), VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); } private void drawRight(@NotNull GuiGraphics gfx, String text, int line) { - drawLine(gfx, Component.literal(text), this.width - HORIZONTAL_OFFSET - mc.font.width(text), VERTICAL_OFFSET + line * (mc.font.lineHeight + 2)); + this.drawLine(gfx, Component.literal(text), this.width - HORIZONTAL_OFFSET - this.mc.font.width(text), VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); + } + + private void drawRight(@NotNull GuiGraphics gfx, Component text, int line) { + this.drawLine(gfx, text, this.width - HORIZONTAL_OFFSET - this.mc.font.width(text), VERTICAL_OFFSET + line * (this.mc.font.lineHeight + 2)); } protected abstract void drawLine(@NotNull GuiGraphics gfx, Component text, int x, int y); + + public void left(GuiGraphics gfx, MutableComponent component) { + this.drawLeft(gfx, component, this.left++); + } + + public void right(GuiGraphics gfx, MutableComponent component) { + this.drawRight(gfx, component, this.right++); + } + + public void top(GuiGraphics gfx, MutableComponent component) { + this.drawRight(gfx, component, this.top++); + } } diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/client/registry/FormatterRegistry.java b/common/src/main/java/com/ultreon/mods/advanceddebug/client/registry/FormatterRegistry.java index 354ff11..076e56f 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/client/registry/FormatterRegistry.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/client/registry/FormatterRegistry.java @@ -3,8 +3,8 @@ import com.ultreon.libs.collections.v0.maps.OrderedHashMap; import com.ultreon.mods.advanceddebug.api.client.menu.Formatter; import com.ultreon.mods.advanceddebug.api.client.registry.IFormatterRegistry; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Map; public class FormatterRegistry implements IFormatterRegistry { diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/init/ModInspectionInit.java b/common/src/main/java/com/ultreon/mods/advanceddebug/init/ModInspectionInit.java new file mode 100644 index 0000000..51e7dc6 --- /dev/null +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/init/ModInspectionInit.java @@ -0,0 +1,236 @@ +package com.ultreon.mods.advanceddebug.init; + +import com.google.common.collect.Lists; +import com.mojang.authlib.GameProfile; +import com.mojang.blaze3d.platform.Window; +import com.ultreon.data.types.*; +import com.ultreon.libs.commons.v0.tuple.Pair; +import com.ultreon.libs.commons.v0.tuple.Quadruple; +import com.ultreon.libs.commons.v0.tuple.Quintuple; +import com.ultreon.libs.commons.v0.tuple.Triple; +import com.ultreon.mods.advanceddebug.inspect.InspectionRoot; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.player.RemotePlayer; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.*; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.entity.animal.CatVariant; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.schedule.Activity; +import net.minecraft.world.food.FoodData; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BannerPattern; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; +import net.minecraft.world.level.material.Fluid; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class ModInspectionInit { + public static void registerAutoFillers() { + InspectionRoot.registerAutoFill(Collection.class, node -> { + node.create("size", Collection::size); + node.createNode("items", Collection::toArray); + }); + + InspectionRoot.registerAutoFill(Map.class, node -> { + node.create("size", Map::size); + node.createNode("entries", mapType -> mapType.entrySet().toArray()); + }); + InspectionRoot.registerAutoFill(Map.Entry.class, node -> { + try { + node.create(InspectionRoot.format(node.getValue().getKey()), Map.Entry::getValue); + } catch (Throwable e) { + node.create("failure", e::getMessage); + } + }); + InspectionRoot.registerAutoFill(Entity.class, node -> { + node.create("type", Entity::getType); + node.create("x", Entity::getX); + node.create("y", Entity::getY); + node.create("z", Entity::getZ); + node.create("airSupply", Entity::getAirSupply); + node.create("maxAirSupply", Entity::getMaxAirSupply); + node.create("maxAirSupply", Entity::getMaxAirSupply); + }); + InspectionRoot.registerAutoFill(LivingEntity.class, node -> { + node.createNode("armorCoverPercentage", LivingEntity::getActiveEffectsMap); + node.createNode("offhandItem", LivingEntity::getOffhandItem); + node.createNode("mainHandItem", LivingEntity::getMainHandItem); + node.createNode("useItem", LivingEntity::getUseItem); + node.create("useItemRemainTicks", LivingEntity::getUseItemRemainingTicks); + node.create("health", LivingEntity::getHealth); + node.create("maxHealth", LivingEntity::getMaxHealth); + node.create("absorptionAmount", LivingEntity::getAbsorptionAmount); + node.create("experienceReward", LivingEntity::getExperienceReward); + node.create("armorValue", LivingEntity::getArmorValue); + node.create("armorCoverPercentage", LivingEntity::getArmorCoverPercentage); + }); + InspectionRoot.registerAutoFill(Player.class, node -> { + node.createNode("gameProfile", Player::getGameProfile); + node.createNode("foodData", Player::getFoodData); + node.create("score", Player::getScore); + node.create("name", Player::getName); + }); + InspectionRoot.registerAutoFill(FoodData.class, node -> { + node.create("food", FoodData::getFoodLevel); + node.create("saturation", FoodData::getSaturationLevel); + node.create("exhaustion", FoodData::getExhaustionLevel); + }); + InspectionRoot.registerAutoFill(ServerData.class, node -> { + node.create("isLan", ServerData::isLan); + }); + InspectionRoot.registerAutoFill(MobEffectInstance.class, node -> { + node.create("effect", MobEffectInstance::getEffect); + node.create("amplifier", MobEffectInstance::getAmplifier); + node.create("duration", MobEffectInstance::getDuration); + }); + InspectionRoot.registerAutoFill(BlockEntity.class, node -> { + node.createNode("state", BlockEntity::getBlockState); + node.createNode("level", BlockEntity::getLevel); + node.create("type", BlockEntity::getType); + node.create("pos", BlockEntity::getBlockPos); + node.create("removed", BlockEntity::isRemoved); + }); + InspectionRoot.registerAutoFill(MinecraftServer.class, node -> { + node.createNode("players", server -> server.getPlayerList().getPlayers().toArray()); + node.createNode("levels", server -> Lists.newArrayList(server.getAllLevels()).toArray()); + node.createNode("gameRules", MinecraftServer::getGameRules); + node.create("tickCount", MinecraftServer::getTickCount); + }); + InspectionRoot.registerAutoFill(Level.class, node -> { + node.createNode("players", Level::players); + node.create("day", Level::isDay); + node.create("night", Level::isNight); + node.create("raining", Level::isRaining); + node.create("thundering", Level::isThundering); + node.create("isDebug", Level::isDebug); + node.create("dayTime", Level::getDayTime); + node.create("gameTime", Level::getGameTime); + node.create("height", Level::getHeight); + node.create("seaLevel", Level::getSeaLevel); + }); + InspectionRoot.registerAutoFill(ServerLevel.class, node -> { + node.createNode("entities", level -> Lists.newArrayList(level.getAllEntities()).toArray()); + node.createNode("dragons", ServerLevel::getDragons); + node.create("isFlat", ServerLevel::isFlat); + node.create("handlingTick", ServerLevel::isHandlingTick); + node.create("canSleepThroughNights", ServerLevel::canSleepThroughNights); + }); + InspectionRoot.registerAutoFill(ServerLevel.class, node -> { + node.createNode("entities", level -> Lists.newArrayList(level.getAllEntities()).toArray()); + node.createNode("dragons", ServerLevel::getDragons); + node.create("isFlat", ServerLevel::isFlat); + node.create("handlingTick", ServerLevel::isHandlingTick); + node.create("canSleepThroughNights", ServerLevel::canSleepThroughNights); + }); + InspectionRoot.registerAutoFill(Window.class, node -> { + node.create("handle", Window::getWindow); + node.create("fpsLimit", Window::getFramerateLimit); + node.create("guiScale", Window::getGuiScale); + node.create("scaledWidth", Window::getGuiScaledWidth); + node.create("scaledHeight", Window::getGuiScaledHeight); + node.create("refreshRate", Window::getRefreshRate); + }); + InspectionRoot.registerAutoFill(GameProfile.class, node -> { + node.create("id", GameProfile::getId); + node.create("name", GameProfile::getName); + }); + InspectionRoot.registerAutoFill(Inventory.class, node -> { + node.create("items", element -> element.items); + node.create("armor", element -> element.armor); + }); + InspectionRoot.registerAutoFill(ItemStack.class, node -> { + node.createNode("tag", ItemStack::getTag); + node.create("item", ItemStack::getItem); + node.create("count", ItemStack::getCount); + node.create("damageValue", ItemStack::getDamageValue); + }); + InspectionRoot.registerAutoFill(CompoundTag.class, node -> { + node.create("size", CompoundTag::size); + node.createNode("entries", mapType -> mapType.getAllKeys().stream().map(key -> Map.entry(key, mapType.get(key))).toArray()); + }); + InspectionRoot.registerAutoFill(ListTag.class, node -> { + node.create("size", ListTag::size); + node.createNode("items", AbstractCollection::toArray); + }); + InspectionRoot.registerAutoFill(LocalPlayer.class, node -> { + node.create("currentMood", LocalPlayer::getCurrentMood); + node.create("waterVision", LocalPlayer::getWaterVision); + node.create("visualRotYInDeg", LocalPlayer::getVisualRotationYInDegrees); + node.create("spinningFxIntensity", element -> element.spinningEffectIntensity); + }); + } + + public void registerFormatters() { + InspectionRoot.registerFormatter(Entity.class, element -> "Entity: " + element.getId()); + InspectionRoot.registerFormatter(MobEffect.class, element -> "Mob Effect: " + BuiltInRegistries.MOB_EFFECT.getKey(element)); + InspectionRoot.registerFormatter(Block.class, element -> "Block: " + BuiltInRegistries.BLOCK.getKey(element)); + InspectionRoot.registerFormatter(Item.class, element -> "Item: " + BuiltInRegistries.ITEM.getKey(element)); + InspectionRoot.registerFormatter(ItemLike.class, element -> "Item Like: " + BuiltInRegistries.ITEM.getKey(element.asItem())); + InspectionRoot.registerFormatter(EntityType.class, element -> "Item Like: " + BuiltInRegistries.ENTITY_TYPE.getKey(element)); + InspectionRoot.registerFormatter(Enchantment.class, element -> "Enchantment: " + BuiltInRegistries.ENCHANTMENT.getKey(element)); + InspectionRoot.registerFormatter(Potion.class, element -> "Enchantment: " + BuiltInRegistries.POTION.getKey(element)); + InspectionRoot.registerFormatter(PoiType.class, element -> "POI Type: " + BuiltInRegistries.POINT_OF_INTEREST_TYPE.getKey(element)); + InspectionRoot.registerFormatter(Attribute.class, element -> "Attribute: " + BuiltInRegistries.ATTRIBUTE.getKey(element)); + InspectionRoot.registerFormatter(Activity.class, element -> "Activity: " + BuiltInRegistries.ACTIVITY.getKey(element)); + InspectionRoot.registerFormatter(BannerPattern.class, element -> "Banner Pattern: " + BuiltInRegistries.BANNER_PATTERN.getKey(element)); + InspectionRoot.registerFormatter(Fluid.class, element -> "Fluid: " + BuiltInRegistries.FLUID.getKey(element)); + InspectionRoot.registerFormatter(CatVariant.class, element -> "Cat Variant: " + BuiltInRegistries.CAT_VARIANT.getKey(element)); + InspectionRoot.registerFormatter(Feature.class, element -> "WorldGen Feature: " + BuiltInRegistries.FEATURE.getKey(element)); + InspectionRoot.registerFormatter(PlacementModifierType.class, element -> "Placement Modifier Type: " + BuiltInRegistries.PLACEMENT_MODIFIER_TYPE.getKey(element)); + InspectionRoot.registerFormatter(PlacementModifier.class, element -> "Placement Modifier: " + BuiltInRegistries.PLACEMENT_MODIFIER_TYPE.getKey(element.type())); + + InspectionRoot.registerFormatter(Pair.class, element -> element.getFirst() + "," + element.getSecond()); + InspectionRoot.registerFormatter(Triple.class, element -> element.getFirst() + "," + element.getSecond() + "," + element.getThird()); + InspectionRoot.registerFormatter(Quadruple.class, element -> element.getFirst() + "," + element.getSecond() + "," + element.getThird() + "," + element.getFourth()); + InspectionRoot.registerFormatter(Quintuple.class, element -> element.getFirst() + "," + element.getSecond() + "," + element.getThird() + "," + element.getFourth() + "," + element.getFifth()); + InspectionRoot.registerFormatter(Enum.class, Enum::name); + InspectionRoot.registerFormatter(ByteType.class, byteType -> Byte.toString(byteType.getValue())); + InspectionRoot.registerFormatter(ShortType.class, shortType -> Short.toString(shortType.getValue())); + InspectionRoot.registerFormatter(IntType.class, intType -> Integer.toString(intType.getValue())); + InspectionRoot.registerFormatter(LongType.class, longType -> Long.toString(longType.getValue())); + InspectionRoot.registerFormatter(FloatType.class, floatType -> Float.toString(floatType.getValue())); + InspectionRoot.registerFormatter(DoubleType.class, doubleType -> Double.toString(doubleType.getValue())); + InspectionRoot.registerFormatter(BooleanType.class, booleanType -> Boolean.toString(booleanType.getValue())); + InspectionRoot.registerFormatter(CharType.class, charType -> Character.toString(charType.getValue())); + InspectionRoot.registerFormatter(StringType.class, stringType -> "\"" + stringType.getValue() + "\""); + InspectionRoot.registerFormatter(UUIDType.class, uuidType -> uuidType.getValue().toString()); + + InspectionRoot.registerFormatter(ByteTag.class, byteType -> Byte.toString(byteType.getAsByte())); + InspectionRoot.registerFormatter(ShortTag.class, shortType -> Short.toString(shortType.getAsShort())); + InspectionRoot.registerFormatter(IntTag.class, intType -> Integer.toString(intType.getAsInt())); + InspectionRoot.registerFormatter(LongTag.class, longType -> Long.toString(longType.getAsLong())); + InspectionRoot.registerFormatter(FloatTag.class, floatType -> Float.toString(floatType.getAsFloat())); + InspectionRoot.registerFormatter(DoubleTag.class, doubleType -> Double.toString(doubleType.getAsDouble())); + InspectionRoot.registerFormatter(StringTag.class, stringType -> "\"" + stringType.getAsString() + "\""); + InspectionRoot.registerFormatter(Player.class, player -> player.getName() + " (" + player.getUUID() + ")"); + InspectionRoot.registerFormatter(ServerPlayer.class, player -> player.getName() + " (" + player.getUUID() + ")"); + InspectionRoot.registerFormatter(LocalPlayer.class, player -> player.getName() + " (" + player.getUUID() + ")"); + InspectionRoot.registerFormatter(RemotePlayer.class, player -> player.getName() + " (" + player.getUUID() + ")"); + InspectionRoot.registerFormatter(AbstractClientPlayer.class, player -> player.getName() + " (" + player.getUUID() + ")"); + } +} diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionNode.java b/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionNode.java new file mode 100644 index 0000000..f28af6d --- /dev/null +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionNode.java @@ -0,0 +1,172 @@ +package com.ultreon.mods.advanceddebug.inspect; + +import com.google.common.base.Suppliers; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Represents a node in the inspection tree. + */ +public class InspectionNode { + private final ConcurrentMap> nodes = new ConcurrentHashMap<>(); + private final ConcurrentMap> elements = new ConcurrentHashMap<>(); + private final String name; + private final InspectionRoot root; + private final @Nullable InspectionNode parent; + private final Function, T> value; + private final Consumer> filler; + private boolean hasFilled; + + /** + * Constructs a new inspection node. + * + * @param name the name of the inspection node. + * @param parent the parent of the inspection node. + * @param root the inspection root. + * @param value the value of the inspection node + */ + public InspectionNode(String name, @Nullable InspectionNode parent, @UnknownNullability InspectionRoot root, + Function, T> value) { + this.name = name; + this.parent = parent; + this.root = root; + this.value = value; + this.filler = n -> { + }; + } + + public InspectionNode(String name, @Nullable InspectionNode parent, @UnknownNullability InspectionRoot root, + Function, T> value, Consumer> filler) { + this.name = name; + this.parent = parent; + this.root = root; + this.value = value; + this.filler = filler; + } + + /** + * Create an element in the inspection node. + * Uses supplier for dynamic values. + * + * @param name the name of the element. + * @param value supplier of the element value. + */ + public void create(String name, Supplier<@Nullable Object> value) { + this.elements.putIfAbsent(name, () -> { + try { + return InspectionRoot.format(value.get()); + } catch (Throwable t) { + return "ERROR"; + } + }); + } + + public void create(String name, NodeMapping value) { + this.elements.putIfAbsent(name, () -> { + try { + return InspectionRoot.format(value.map(this.value.apply(this))); + } catch (Throwable t) { + return "ERROR"; + } + }); + } + + /** + * Create an element in the inspection node. + * + * @param name the name of the element. + * @param value the element value. + * @deprecated not recommended to use. Use {@link #create(String, Supplier)} instead. + */ + @Deprecated + public void create(String name, @Nullable Object value) { + this.elements.putIfAbsent(name, Suppliers.memoizeWithExpiration(() -> InspectionRoot.format(value), 2, TimeUnit.SECONDS)); + } + + /** + * Remove a node from the inspection node. + * + * @param name the name of the node to remove. + * @return true if the element was present, false otherwise. + */ + @CanIgnoreReturnValue + public boolean remove(String name) { + return this.elements.remove(name) != null; + } + + @SafeVarargs + @CanIgnoreReturnValue + public final InspectionNode createNode(String name, Supplier value, C... typeGetter) { + return this._createNode(name, typeGetter, Suppliers.memoizeWithExpiration(value::get, 2, TimeUnit.SECONDS)); + } + + @SafeVarargs + @CanIgnoreReturnValue + public final InspectionNode createNode(String name, NodeMapping value, C... typeGetter) { + return this._createNode(name, typeGetter, Suppliers.memoizeWithExpiration(() -> { + Function, T> v = this.value; + return value.map(v.apply(this)); + }, 2, TimeUnit.SECONDS)); + } + + @NotNull + private InspectionNode<@Nullable C> _createNode(String name, C[] typeGetter, Supplier val) { + var node = new InspectionNode<@Nullable C>(name, this, this.getRoot(), n -> val.get(), n -> InspectionRoot.fill(n, typeGetter.getClass().getComponentType())); + this.nodes.put(name, node); + return node; + } + + @CanIgnoreReturnValue + public @Nullable InspectionNode removeNode(String name) { + return this.nodes.remove(name); + } + + public Map> getNodes() { + if (!this.hasFilled) { + this.filler.accept(this); + this.hasFilled = true; + } + return this.nodes; + } + + public Map> getElements() { + return this.elements; + } + + public String getName() { + return this.name; + } + + /** + * @return the parent of the inspection node, or {@code null} if the inspection node is the root. + */ + public @Nullable InspectionNode getParent() { + return this.parent; + } + + /** + * @return the root of the inspection tree. + */ + public InspectionRoot getRoot() { + return this.root; + } + + public T getValue() { + return this.value.apply(this); + } + + @FunctionalInterface + public interface NodeMapping { + @Nullable C map(@NotNull T value); + } +} diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionRoot.java b/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionRoot.java new file mode 100644 index 0000000..add2090 --- /dev/null +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/inspect/InspectionRoot.java @@ -0,0 +1,173 @@ +package com.ultreon.mods.advanceddebug.inspect; + +import com.ultreon.mods.advanceddebug.util.McDisposable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Array; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * The root node of the inspection tree. + * Inspection trees are a way of determining values within the game. + * It also allows for inspecting game packets. + * + * @author XyperCode + * @since 0.1.0 + */ +public final class InspectionRoot extends InspectionNode implements McDisposable { + private boolean inspecting; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private static final Map, Consumer>> AUTO_FILL = new ConcurrentHashMap<>(); + private static final Map, Function> FORMATTERS = new ConcurrentHashMap<>(); + + /** + * Constructs a new root inspection node. + * + * @param rootValue the root node value. + */ + public InspectionRoot(T rootValue) { + super("", null, null, n -> rootValue); + } + + /** + * Format a value using the formatters registered with {@link #registerFormatter(Class, Function)}. + * + * @param o the value to format. + * @return the formatted value. + */ + @SuppressWarnings("unchecked") + public static String format(@Nullable Object o) { + if (o == null) { + return "null"; + } + + var formatter = (Function) InspectionRoot.FORMATTERS.get(o.getClass()); + if (formatter != null) { + try { + return formatter.apply(o); + } catch (Throwable ignored) { + + } + } + try { + return String.valueOf(o); + } catch (Throwable t) { + return o.getClass().getName(); + } + } + + public boolean isInspecting() { + return this.inspecting; + } + + public void setInspecting(boolean inspecting) { + this.inspecting = inspecting; + } + + @Override + public @NotNull InspectionRoot getRoot() { + return this; + } + + static void fill(InspectionNode node, Class clazz) { + if (clazz.isArray()) { + var componentType = clazz.getComponentType(); + try { + var nodeValue = node.getValue(); + var length = Array.getLength(nodeValue); + for (var i = 0; i < length; i++) { + try { + var value = Array.get(nodeValue, i); + InspectionNode created; + if (value != null && value.getClass().isArray()) { + created = node.createNode(i + " (Array)", () -> value); + } else if (value != null) { + created = node.createNode(i + " (" + format(value) + ")", () -> value); + } else { + created = node.createNode(i + " (null)", () -> "null"); + } + InspectionRoot.fill(created, componentType); + } catch (Throwable e) { + node.create("failure", e::getMessage); + } + } + } catch (Throwable e) { + node.create("failure", e::getMessage); + } + } + + var filler = InspectionRoot.AUTO_FILL.get(clazz); + if (filler != null) { + filler.accept(node); + } + + while (clazz != null) { + filler = InspectionRoot.AUTO_FILL.get(clazz); + if (filler != null) { + filler.accept(node); + } + clazz = clazz.getSuperclass(); + } + } + + /** + * Register an object formatter for a given class. + * + * @param clazz the class to format. + * @param formatter the formatter to register. + * @param the type of the object to format. + */ + public static void registerFormatter(Class clazz, Function formatter) { + InspectionRoot.FORMATTERS.put(clazz, formatter); + } + + /** + * Register an auto-filler for a given class. + * + * @param clazz the class to autofill. + * @param filler the auto-filler to register + * @param the type of the node. + */ + @SuppressWarnings("unchecked") + public static void registerAutoFill(Class clazz, Consumer<@NotNull InspectionNode> filler) { + InspectionRoot.AUTO_FILL.put(clazz, inspectionNode -> filler.accept((InspectionNode) inspectionNode)); + } + + /** + * Get the node at the given path. + * + * @param path the path of the node. + * @return the node at the given path, or {@code null} if the path is invalid. + */ + public @Nullable InspectionNode getNode(String path) { + if (path.equals("/")) return this; + if (path.endsWith("/")) path = path.substring(0, path.length() - 1); + if (!path.matches("(/[^/]+)+")) throw new IllegalArgumentException("Invalid path: " + path); + + var pathParts = path.substring(1).split("/"); + InspectionNode node = null; + for (var name : pathParts) { + node = Objects.requireNonNullElse(node, this).getNodes().get(name); + + if (node == null) return null; + } + + if (node == null) throw new InternalError("Profile section not found: " + path); + + return node; + } + + /** + * Dispose the inspection tree. + */ + public void dispose() { + this.scheduler.shutdownNow(); + } +} diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/mixin/common/GameRendererMixin.java b/common/src/main/java/com/ultreon/mods/advanceddebug/mixin/common/GameRendererMixin.java index 35be07e..f967715 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/mixin/common/GameRendererMixin.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/mixin/common/GameRendererMixin.java @@ -65,9 +65,14 @@ public class GameRendererMixin { @Inject(method = "render", at = @At("RETURN")) private void advancedDebug$injectImGuiRender$return(float partialTicks, long nanoTime, boolean renderLevel, CallbackInfo ci) { - boolean toggleKey = glfwGetKey(minecraft.getWindow().getWindow(), GLFW_KEY_F12) == GLFW_TRUE; + boolean toggleKey = glfwGetKey(minecraft.getWindow().getWindow(), GLFW_KEY_F12) == GLFW_PRESS; if (advanced_debug$wasTogglePressed && !toggleKey) { advanced_debug$wasTogglePressed = false; + + if (glfwGetKey(this.minecraft.getWindow().getWindow(), GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) { + DebugGui.SHOW_OBJECT_INSPECTION.set(!DebugGui.SHOW_OBJECT_INSPECTION.get()); + return; + } DebugGui.SHOW_IM_GUI.set(!DebugGui.SHOW_IM_GUI.get()); } else if (!advanced_debug$wasTogglePressed && toggleKey) { advanced_debug$wasTogglePressed = true; diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/util/ImGuiEx.java b/common/src/main/java/com/ultreon/mods/advanceddebug/util/ImGuiEx.java index a151b32..76147fa 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/util/ImGuiEx.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/util/ImGuiEx.java @@ -1,17 +1,8 @@ package com.ultreon.mods.advanceddebug.util; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; -import com.ultreon.libs.functions.v0.consumer.BooleanConsumer; -import com.ultreon.libs.functions.v0.consumer.ByteConsumer; +import com.ultreon.libs.commons.v0.util.EnumUtils; import com.ultreon.libs.functions.v0.consumer.DoubleConsumer; -import com.ultreon.libs.functions.v0.consumer.FloatConsumer; -import com.ultreon.libs.functions.v0.consumer.ShortConsumer; - -import java.io.File; -import java.io.IOException; -import java.util.function.*; - +import com.ultreon.libs.functions.v0.consumer.*; import com.ultreon.libs.functions.v0.supplier.ByteSupplier; import com.ultreon.libs.functions.v0.supplier.FloatSupplier; import com.ultreon.libs.functions.v0.supplier.ShortSupplier; @@ -21,13 +12,7 @@ import imgui.extension.imguifiledialog.flag.ImGuiFileDialogFlags; import imgui.flag.ImGuiDataType; import imgui.flag.ImGuiInputTextFlags; -import imgui.type.ImBoolean; -import imgui.type.ImDouble; -import imgui.type.ImFloat; -import imgui.type.ImInt; -import imgui.type.ImLong; -import imgui.type.ImShort; -import imgui.type.ImString; +import imgui.type.*; import net.minecraft.ResourceLocationException; import net.minecraft.client.Minecraft; import net.minecraft.nbt.CompoundTag; @@ -38,6 +23,14 @@ import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.function.IntConsumer; +import java.util.function.LongConsumer; +import java.util.function.*; + public class ImGuiEx { private static final ImGuiFileDialogPaneFun DUMP_NBT_CALLBACK = new ImGuiFileDialogPaneFun() { @Override @@ -392,4 +385,58 @@ public static void slider(String label, String id, int value, int min, int max, onChange.accept(v[0]); } } + + public static void button(String label, String id, Runnable func) { + ImGui.text(label); + ImGui.sameLine(); + try { + if (ImGui.button("##" + id, 120, 16)) { + func.run(); + } + } catch (Exception e) { + ImGui.text(String.valueOf(e)); + } + } + + public static void editColor3(String color, String s, Supplier<@NotNull Color> getter, Consumer<@NotNull Color> setter) { + ImGui.text(color); + ImGui.sameLine(); + try { + Color c = getter.get(); + float[] floats = {c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, 1f}; + if (ImGui.colorEdit3("##" + s, floats)) { + setter.accept(new Color(floats[0], floats[1], floats[2], 1f)); + } + } catch (Exception e) { + ImGui.text(String.valueOf(e)); + } + } + + public static void editColor4(String color, String s, Supplier getter, Consumer setter) { + ImGui.text(color); + ImGui.sameLine(); + try { + Color c = getter.get(); + float[] floats = {c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f, c.getAlpha() / 255f}; + if (ImGui.colorEdit4("##" + s, floats)) { + setter.accept(new Color(floats[0], floats[1], floats[2], floats[3])); + } + } catch (Exception e) { + ImGui.text(String.valueOf(e)); + } + } + + public static > void editEnum(String s, String s1, Supplier getter, Consumer setter) { + ImGui.text(s); + ImGui.sameLine(); + try { + T e = getter.get(); + ImInt index = new ImInt(e.ordinal()); + if (ImGui.combo("##" + s1, index, Arrays.stream(e.getClass().getEnumConstants()).map(Enum::name).toArray(String[]::new))) { + setter.accept(EnumUtils.byOrdinal(index.get(), e)); + } + } catch (Exception e) { + ImGui.text(String.valueOf(e)); + } + } } diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/util/McDisposable.java b/common/src/main/java/com/ultreon/mods/advanceddebug/util/McDisposable.java new file mode 100644 index 0000000..8f3468e --- /dev/null +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/util/McDisposable.java @@ -0,0 +1,5 @@ +package com.ultreon.mods.advanceddebug.util; + +public interface McDisposable { + void dispose(); +} diff --git a/common/src/main/java/com/ultreon/mods/advanceddebug/util/SelectedBlock.java b/common/src/main/java/com/ultreon/mods/advanceddebug/util/SelectedBlock.java index 7de908e..a208ab5 100644 --- a/common/src/main/java/com/ultreon/mods/advanceddebug/util/SelectedBlock.java +++ b/common/src/main/java/com/ultreon/mods/advanceddebug/util/SelectedBlock.java @@ -29,7 +29,7 @@ public BlockPos getPos() { } public BlockEntity getBlockEntity() { - return level.getBlockEntity(pos); + return level.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE); } public BlockState getBlockState() { diff --git a/fabric/build.gradle b/fabric/build.gradle index efe13e3..9df1afc 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -76,18 +76,18 @@ processResources { shadowJar { configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" + archiveClassifier = "dev-shadow" } remapJar { injectAccessWidener = true input.set shadowJar.archiveFile dependsOn shadowJar - classifier null + archiveClassifier = null } jar { - classifier "dev" + archiveClassifier = "dev" } sourcesJar { @@ -113,5 +113,6 @@ publishing { // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { // Add repositories to publish to here. + mavenLocal() } } \ No newline at end of file diff --git a/forge/build.gradle b/forge/build.gradle index 1e37ed2..61d2e53 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -68,17 +68,17 @@ shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" + archiveClassifier = "dev-shadow" } remapJar { input.set shadowJar.archiveFile dependsOn shadowJar - classifier null + archiveClassifier = null } jar { - classifier "dev" + archiveClassifier = "dev" manifest { attributes([ "Specification-Title" : "Ultreon Modding Lib", @@ -116,5 +116,6 @@ publishing { // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. repositories { // Add repositories to publish to here. + mavenLocal() } } diff --git a/gradle.properties b/gradle.properties index 3ca089e..e905228 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.jvmargs=-Xmx5G minecraft_version=1.20.1 archives_base_name=advanced-debug -mod_version=2.4.0 +mod_version=2.5.0 maven_group=com.ultreon.mods architectury_version=9.1.12 fabric_loader_version=0.14.21 @@ -18,3 +18,5 @@ mod_description=More advanced debug overlay, with way more information. Also has ultreonlib_version=1.5.0 imgui_version=1.86.10 +ultreon_data_version=0.1.0 +corelibs_version=0.1.0 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102..db9a6b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists