aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin
diff options
context:
space:
mode:
authorRoman / Linnea Gräf <roman.graef@gmail.com>2023-03-04 02:54:50 +0100
committerGitHub <noreply@github.com>2023-03-04 12:54:50 +1100
commit5dd063fbba6bde64806a7620541dc2d9bdf42871 (patch)
tree01aee1a743a32a0b2546513c59a43559ce3085fe /src/main/kotlin
parentdb86c98e0c72b18663ef26cd46cef7d53c1d6414 (diff)
downloadNotEnoughUpdates-5dd063fbba6bde64806a7620541dc2d9bdf42871.tar.gz
NotEnoughUpdates-5dd063fbba6bde64806a7620541dc2d9bdf42871.tar.bz2
NotEnoughUpdates-5dd063fbba6bde64806a7620541dc2d9bdf42871.zip
Replace all commands in NEU with a brigadier implementation (#599)
Diffstat (limited to 'src/main/kotlin')
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.kt256
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.kt78
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/NEUStatsCommand.kt209
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/PackDevCommand.kt165
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/SimpleDevCommands.kt110
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/FeaturesCommand.kt65
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/HelpCommand.kt95
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/LinksCommand.kt52
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/SettingsCommand.kt53
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/help/StorageViewerWhyCommand.kt48
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/AhCommand.kt67
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/DungeonCommands.kt144
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/FairySoulsCommand.kt62
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/MiscCommands.kt169
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/PeekCommand.kt318
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/ProfileViewerCommands.kt87
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/commands/misc/ScreenOpenCommands.kt52
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/events/RegisterBrigadierCommandEvent.kt52
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/BrigadierRoot.kt103
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/EnumArgumentType.kt64
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/RestArgumentType.kt31
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/brigadier/dsl.kt179
-rw-r--r--src/main/kotlin/io/github/moulberry/notenoughupdates/util/iterate.kt28
23 files changed, 2487 insertions, 0 deletions
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.kt
new file mode 100644
index 00000000..805ff114
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.commands.dev
+
+import com.mojang.brigadier.arguments.StringArgumentType
+import io.github.moulberry.notenoughupdates.BuildFlags
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.core.config.GuiPositionEditor
+import io.github.moulberry.notenoughupdates.core.util.MiscUtils
+import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent
+import io.github.moulberry.notenoughupdates.miscfeatures.FishingHelper
+import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.CustomBiomes
+import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.LocationChangeEvent
+import io.github.moulberry.notenoughupdates.miscgui.GuiPriceGraph
+import io.github.moulberry.notenoughupdates.miscgui.minionhelper.MinionHelperManager
+import io.github.moulberry.notenoughupdates.util.PronounDB
+import io.github.moulberry.notenoughupdates.util.SBInfo
+import io.github.moulberry.notenoughupdates.util.TabListUtils
+import io.github.moulberry.notenoughupdates.util.brigadier.*
+import net.minecraft.client.Minecraft
+import net.minecraft.client.gui.GuiScreen
+import net.minecraft.command.ICommandSender
+import net.minecraft.entity.player.EntityPlayer
+import net.minecraft.launchwrapper.Launch
+import net.minecraft.util.ChatComponentText
+import net.minecraft.util.EnumChatFormatting.*
+import net.minecraft.util.EnumParticleTypes
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.function.Predicate
+import kotlin.math.floor
+
+@NEUAutoSubscribe
+class DevTestCommand {
+ companion object {
+ val DEV_TESTERS: List<String> = mutableListOf(
+ "d0e05de7-6067-454d-beae-c6d19d886191", // moulberry
+ "66502b40-6ac1-4d33-950d-3df110297aab", // lucycoconut
+ "a5761ff3-c710-4cab-b4f4-3e7f017a8dbf", // ironm00n
+ "5d5c548a-790c-4fc8-bd8f-d25b04857f44", // ariyio
+ "53924f1a-87e6-4709-8e53-f1c7d13dc239", // throwpo
+ "d3cb85e2-3075-48a1-b213-a9bfb62360c1", // lrg89
+ "0b4d470f-f2fb-4874-9334-1eaef8ba4804", // dediamondpro
+ "ebb28704-ed85-43a6-9e24-2fe9883df9c2", // lulonaut
+ "698e199d-6bd1-4b10-ab0c-52fedd1460dc", // craftyoldminer
+ "8a9f1841-48e9-48ed-b14f-76a124e6c9df", // eisengolem
+ "a7d6b3f1-8425-48e5-8acc-9a38ab9b86f7", // whalker
+ "0ce87d5a-fa5f-4619-ae78-872d9c5e07fe", // ascynx
+ "a049a538-4dd8-43f8-87d5-03f09d48b4dc", // egirlefe
+ "7a9dc802-d401-4d7d-93c0-8dd1bc98c70d", // efefury
+ "bb855349-dfd8-4125-a750-5fc2cf543ad5" // hannibal2
+ )
+ val SPECIAL_KICK = "SPECIAL_KICK"
+
+ val DEV_FAIL_STRINGS = arrayOf(
+ "No.",
+ "I said no.",
+ "You aren't allowed to use this.",
+ "Are you sure you want to use this? Type 'Yes' in chat.",
+ "Are you sure you want to use this? Type 'Yes' in chat.",
+ "Lmao you thought",
+ "Ok please stop",
+ "What do you want from me?",
+ "This command almost certainly does nothing useful for you",
+ "Ok, this is the last message, after this it will repeat",
+ "No.",
+ "I said no.",
+ "Dammit. I thought that would work. Uhh...",
+ "\u00a7dFrom \u00a7c[ADMIN] Minikloon\u00a77: If you use that command again, I'll have to ban you",
+ SPECIAL_KICK,
+ "Ok, this is actually the last message, use the command again and you'll crash I promise"
+ )
+
+ fun isDeveloper(commandSender: ICommandSender): Boolean {
+ return DEV_TESTERS.contains((commandSender as? EntityPlayer)?.uniqueID?.toString())
+ || Launch.blackboard.get("fml.deobfuscatedEnvironment") as Boolean
+
+ }
+ }
+
+ var devFailIndex = 0
+ fun canPlayerExecute(commandSender: ICommandSender): Boolean {
+ return isDeveloper(commandSender)
+ }
+
+ @SubscribeEvent
+ fun onCommands(event: RegisterBrigadierCommandEvent) {
+ val hook = event.command("neudevtest") {
+ requires {
+ canPlayerExecute(it)
+ }
+ thenLiteralExecute("profileinfo") {
+ val currentProfile = SBInfo.getInstance().currentProfile
+ val gamemode = SBInfo.getInstance().getGamemodeForProfile(currentProfile)
+ reply("${GOLD}You are on Profile $currentProfile with the mode $gamemode")
+ }.withHelp("Display information about your current profile")
+ thenLiteralExecute("buildflags") {
+ reply("BuildFlags: \n" +
+ BuildFlags.getAllFlags().entries
+ .joinToString(("\n")) { (key, value) -> " + $key - $value" })
+ }.withHelp("List the flags with which NEU was built")
+ thenLiteral("exteditor") {
+ thenArgument("editor", StringArgumentType.string()) { newEditor ->
+ thenExecute {
+ NotEnoughUpdates.INSTANCE.config.hidden.externalEditor = this[newEditor]
+ reply("You changed your external editor to: §Z${this[newEditor]}")
+ }
+ }.withHelp("Change the editor used to edit repo files")
+ thenExecute {
+ reply("Your external editor is: §Z${NotEnoughUpdates.INSTANCE.config.hidden.externalEditor}")
+ }
+ }.withHelp("See your current external editor for repo files")
+ thenLiteral("pricetest") {
+ thenArgument("item", StringArgumentType.string()) { item ->
+ thenExecute {
+ NotEnoughUpdates.INSTANCE.openGui = GuiPriceGraph(this[item])
+ }
+ }.withHelp("Display the price graph for an item by id")
+ thenExecute {
+ NotEnoughUpdates.INSTANCE.manager.auctionManager.updateBazaar()
+ }
+ }.withHelp("Update the price data from the bazaar")
+ thenLiteralExecute("zone") {
+ val target = Minecraft.getMinecraft().objectMouseOver.blockPos
+ ?: Minecraft.getMinecraft().thePlayer.position
+ val zone = CustomBiomes.INSTANCE.getSpecialZone(target)
+ listOf(
+ ChatComponentText("Showing Zone Info for: $target"),
+ ChatComponentText("Zone: " + (zone?.name ?: "null")),
+ ChatComponentText("Location: " + SBInfo.getInstance().getLocation()),
+ ChatComponentText("Biome: " + CustomBiomes.INSTANCE.getCustomBiome(target))
+ ).forEach { component ->
+ reply(component)
+ }
+ MinecraftForge.EVENT_BUS.post(
+ LocationChangeEvent(
+ SBInfo.getInstance().getLocation(), SBInfo
+ .getInstance()
+ .getLocation()
+ )
+ )
+ }.withHelp("Display information about the special block zone at your cursor (Custom Texture Regions)")
+ thenLiteralExecute("positiontest") {
+ NotEnoughUpdates.INSTANCE.openGui = GuiPositionEditor()
+ }.withHelp("Open the gui position editor")
+ thenLiteral("pt") {
+ thenArgument("particle", EnumArgumentType.enum<EnumParticleTypes>()) { particle ->
+ thenExecute {
+ FishingHelper.type = this[particle]
+ reply("Fishing particles set to ${FishingHelper.type}")
+ }
+ }
+ }
+ thenLiteralExecute("dev") {
+ NotEnoughUpdates.INSTANCE.config.hidden.dev = !NotEnoughUpdates.INSTANCE.config.hidden.dev
+ reply("§e[NEU] Dev mode " + if (NotEnoughUpdates.INSTANCE.config.hidden.dev) "§aenabled" else "§cdisabled")
+ }.withHelp("Toggle developer mode")
+ thenLiteralExecute("saveconfig") {
+ NotEnoughUpdates.INSTANCE.saveConfig()
+ reply("Config saved")
+ }.withHelp("Force sync the config to disk")
+ thenLiteralExecute("searchmode") {
+ NotEnoughUpdates.INSTANCE.config.hidden.firstTimeSearchFocus = true
+ reply(AQUA.toString() + "I would never search")
+ }.withHelp("Reset your search data to redisplay the search tutorial")
+ thenLiteralExecute("bluehair") {
+ PronounDB.test()
+ }.withHelp("Test the pronoundb integration")
+ thenLiteral("opengui") {
+ thenArgumentExecute("class", StringArgumentType.string()) { className ->
+ try {
+ NotEnoughUpdates.INSTANCE.openGui =
+ Class.forName(this[className]).newInstance() as GuiScreen
+ reply("Opening gui: " + NotEnoughUpdates.INSTANCE.openGui)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ reply("Failed to open this GUI.")
+ }
+ }.withHelp("Open a gui by class name")
+ }
+ thenLiteralExecute("center") {
+ val x = floor(Minecraft.getMinecraft().thePlayer.posX) + 0.5f
+ val z = floor(Minecraft.getMinecraft().thePlayer.posZ) + 0.5f
+ Minecraft.getMinecraft().thePlayer.setPosition(x, Minecraft.getMinecraft().thePlayer.posY, z)
+ reply("Literal hacks")
+ }.withHelp("Center yourself on the block you are currently standing (like using AOTE)")
+ thenLiteral("minion") {
+ thenArgumentExecute("args", RestArgumentType) { arg ->
+ MinionHelperManager.getInstance().handleCommand(arrayOf("minion") + this[arg].split(" "))
+ }.withHelp("Minion related commands. Not yet integrated in brigadier")
+ }
+ thenLiteralExecute("copytablist") {
+ val tabList = TabListUtils.getTabList().joinToString("\n", postfix = "\n")
+ MiscUtils.copyToClipboard(tabList)
+ reply("Copied tablist to clipboard!")
+ }.withHelp("Copy the tab list")
+ thenLiteralExecute("useragent") {
+ thenArgumentExecute("newuseragent", RestArgumentType) { userAgent ->
+ reply("Setting your user agent to ${this[userAgent]}")
+ NotEnoughUpdates.INSTANCE.config.hidden.customUserAgent = this[userAgent]
+ }.withHelp("Set a custom user agent for all HTTP requests")
+ thenExecute {
+ reply("Resetting your user agent.")
+ NotEnoughUpdates.INSTANCE.config.hidden.customUserAgent = null
+ }
+ }.withHelp("Reset the custom user agent")
+ }
+ hook.beforeCommand = Predicate {
+ if (!canPlayerExecute(it.context.source)) {
+ if (devFailIndex !in DEV_FAIL_STRINGS.indices) {
+ throw object : Error("L") {
+ @Override
+ fun printStackTrace() {
+ throw Error("L")
+ }
+ }
+ }
+ val text = DEV_FAIL_STRINGS[devFailIndex++]
+ if (text == SPECIAL_KICK) {
+ val component = ChatComponentText("\u00a7cYou are permanently banned from this server!")
+ component.appendText("\n")
+ component.appendText("\n\u00a77Reason: \u00a7rI told you not to run the command - Moulberry")
+ component.appendText("\n\u00a77Find out more: \u00a7b\u00a7nhttps://www.hypixel.net/appeal")
+ component.appendText("\n")
+ component.appendText("\n\u00a77Ban ID: \u00a7r#49871982")
+ component.appendText("\n\u00a77Sharing your Ban ID may affect the processing of your appeal!")
+ Minecraft.getMinecraft().netHandler.networkManager.closeChannel(component)
+ } else {
+ it.context.source.addChatMessage(ChatComponentText("$RED$text"))
+ }
+ false
+ } else {
+ true
+ }
+ }
+ }
+
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.kt
new file mode 100644
index 00000000..3e5e7b9b
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/DiagCommand.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.commands.dev
+
+import com.mojang.brigadier.arguments.BoolArgumentType.bool
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent
+import io.github.moulberry.notenoughupdates.miscfeatures.CrystalMetalDetectorSolver
+import io.github.moulberry.notenoughupdates.miscfeatures.CrystalWishingCompassSolver
+import io.github.moulberry.notenoughupdates.options.customtypes.NEUDebugFlag
+import io.github.moulberry.notenoughupdates.util.brigadier.*
+import io.github.moulberry.notenoughupdates.util.brigadier.EnumArgumentType.Companion.enum
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+
+// Why is this not merged into /neudevtest
+@NEUAutoSubscribe
+class DiagCommand {
+ @SubscribeEvent
+ fun onCommands(event: RegisterBrigadierCommandEvent) {
+ event.command("neudiag") {
+ thenLiteral("metal") {
+ thenLiteral("center") {
+ thenArgumentExecute("usecenter", bool()) { useCenter ->
+ CrystalMetalDetectorSolver.setDebugDoNotUseCenter(this[useCenter])
+ reply("Center coordinates-based solutions ${if (this[useCenter]) "enabled" else "disabled"}")
+ }.withHelp("Toggle coordinate based solutions")
+ }
+ thenExecute {
+ CrystalMetalDetectorSolver.logDiagnosticData(true)
+ reply("Enabled metal detector diagnostic logging.")
+ }
+ }.withHelp("Enable metal detector diagnostics")
+ thenLiteralExecute("wishing") {
+ CrystalWishingCompassSolver.getInstance().logDiagnosticData(true)
+ reply("Enabled wishing compass diagnostic logging")
+ }.withHelp("Enable wishing compass diagnostic logging")
+ thenLiteral("debug") {
+ thenLiteralExecute("list") {
+ reply("Here are all flags:\n${NEUDebugFlag.getFlagList()}")
+ }.withHelp("List all debug diagnostic logging flags")
+ thenLiteral("setflag") {
+ thenArgument("flag", enum<NEUDebugFlag>()) { flag ->
+ thenArgumentExecute("enable", bool()) { enable ->
+ val debugFlags = NotEnoughUpdates.INSTANCE.config.hidden.debugFlags
+ if (this[enable]) {
+ debugFlags.add(this[flag])
+ } else {
+ debugFlags.remove(this[flag])
+ }
+ reply("${if(this[enable]) "Enabled" else "Disabled"} the flag ${this[flag]}.")
+ }.withHelp("Enable or disable a diagnostic logging stream")
+ }
+ }
+ thenExecute {
+ reply("Effective debug flags: \n${NEUDebugFlag.getEnabledFlags()}")
+ }
+ }.withHelp("Log diagnostic data.")
+ }
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/NEUStatsCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/NEUStatsCommand.kt
new file mode 100644
index 00000000..7035aaa3
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/NEUStatsCommand.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.commands.dev
+
+import com.mojang.brigadier.context.CommandContext
+import com.sun.management.OperatingSystemMXBean
+import com.sun.management.UnixOperatingSystemMXBean
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates
+import io.github.moulberry.notenoughupdates.autosubscribe.NEUAutoSubscribe
+import io.github.moulberry.notenoughupdates.events.RegisterBrigadierCommandEvent
+import io.github.moulberry.notenoughupdates.util.DiscordMarkdownBuilder
+import io.github.moulberry.notenoughupdates.util.HastebinUploader
+import io.github.moulberry.notenoughupdates.util.SBInfo
+import io.github.moulberry.notenoughupdates.util.brigadier.reply
+import io.github.moulberry.notenoughupdates.util.brigadier.thenExecute
+import io.github.moulberry.notenoughupdates.util.brigadier.thenLiteralExecute
+import io.github.moulberry.notenoughupdates.util.brigadier.withHelp
+import net.minecraft.client.Minecraft
+import net.minecraft.client.renderer.OpenGlHelper
+import net.minecraft.command.ICommandSender
+import net.minecraft.util.EnumChatFormatting.DARK_RED
+import net.minecraft.util.EnumChatFormatting.GREEN
+import net.minecraftforge.common.ForgeVersion
+import net.minecraftforge.fml.client.FMLClientHandler
+import net.minecraftforge.fml.common.Loader
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import org.lwjgl.opengl.Display
+import org.lwjgl.opengl.GL11
+import java.awt.Toolkit
+import java.awt.datatransfer.StringSelection
+import java.lang.management.ManagementFactory
+import java.util.concurrent.CompletableFuture
+import javax.management.JMX
+import javax.management.ObjectName
+
+@NEUAutoSubscribe
+class NEUStatsCommand {
+ @SubscribeEvent
+ fun onCommands(event: RegisterBrigadierCommandEvent) {
+ event.command("stats", "neustats") {
+ thenLiteralExecute("modlist") {
+ clipboardAndSendMessage(
+ DiscordMarkdownBuilder()
+ .also(::appendModList)
+ .toString()
+ )
+ }.withHelp("Copy the mod list to your clipboard")
+ thenLiteralExecute("full") {
+ clipboardAndSendMessage(
+ DiscordMarkdownBuilder()
+ .also(::appendStats)
+ .also(::appendModList)
+ .toString()
+ )
+ }.withHelp("Copy the full list of all NEU stats and your mod list to your clipboard")
+ thenLiteralExecute("dump") {
+ reply("${GREEN}This will upload a dump of the java classes your game has loaded how big they are and how many there are. This can take a few seconds as it is uploading to HasteBin.")
+ uploadDataUsageDump().thenAccept {
+ clipboardAndSendMessage(it)
+ }
+ }.withHelp("Dump all loaded classes and their memory usage and copy that to your clipboard.")
+ thenExecute {
+ clipboardAndSendMessage(
+ DiscordMarkdownBuilder()
+ .also(::appendStats)
+ .also {
+ if (Loader.instance().activeModList.size <= 15) appendModList(it)
+ }
+ .toString()
+ )
+ }
+ }.withHelp("Copy a list of NEU relevant stats to your clipboard for debugging purposes")
+ }
+ interface DiagnosticCommandMXBean {
+ fun gcClassHistogram(array: Array<String>): String
+ }
+
+ private fun uploadDataUsageDump(): CompletableFuture<String?> {
+ return CompletableFuture.supplyAsync {
+ try {
+ val server =
+ ManagementFactory.getPlatformMBeanServer()
+ val objectName =
+ ObjectName.getInstance("com.sun.management:type=DiagnosticCommand")
+ val proxy = JMX.newMXBeanProxy(
+ server,
+ objectName,
+ DiagnosticCommandMXBean::class.java
+ )
+ HastebinUploader.upload(
+ proxy.gcClassHistogram(emptyArray()).replace("[", "[]"),
+ HastebinUploader.Mode.NORMAL
+ )
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+ }
+
+
+ private fun getMemorySize(): Long {
+ try {
+ return (ManagementFactory.getOperatingSystemMXBean() as OperatingSystemMXBean).totalPhysicalMemorySize
+ } catch (e: java.lang.Exception) {
+ try {
+ return (ManagementFactory.getOperatingSystemMXBean() as UnixOperatingSystemMXBean).totalPhysicalMemorySize
+ } catch (ignored: java.lang.Exception) { /*IGNORE*/
+ }
+ }
+ return -1
+ }
+
+ val ONE_MB = 1024L * 1024L
+ private fun appendStats(builder: DiscordMarkdownBuilder) {
+ val maxMemory = Runtime.getRuntime().maxMemory()
+ val totalMemory = Runtime.getRuntime().totalMemory()
+ val freeMemory = Runtime.getRuntime().freeMemory()
+ val currentMemory = totalMemory - freeMemory
+ builder.category("System Stats")
+ builder.append("OS", System.getProperty("os.name"))
+ builder.append("CPU", OpenGlHelper.getCpu())
+ builder.append(
+ "Display",
+ String.format("%dx%d (%s)", Display.getWidth(), Display.getHeight(), GL11.glGetString(GL11.GL_VENDOR))
+ )
+ builder.append("GPU", GL11.glGetString(GL11.GL_RENDERER))
+ builder.append("GPU Driver", GL11.glGetString(GL11.GL_VERSION))
+ if (getMemorySize() > 0)
+ builder.append(
+ "Maximum Memory",
+ "${getMemorySize() / ONE_MB}MB"
+ )
+ builder.append("Shaders", ("" + OpenGlHelper.isFramebufferEnabled()).uppercase())
+ builder.category("Java Stats")
+ builder.append(
+ "Java",
+ "${System.getProperty("java.version")} ${if (Minecraft.getMinecraft().isJava64bit) 64 else 32}bit",
+ )
+ builder.append(
+ "Memory", String.format(
+ "% 2d%% %03d/%03dMB",
+ currentMemory * 100L / maxMemory,
+ currentMemory / ONE_MB,
+ maxMemory / ONE_MB
+ )
+ )
+ builder.append(
+ "Memory Allocated",
+ String.format("% 2d%% %03dMB", totalMemory * 100L / maxMemory, totalMemory / ONE_MB)
+ )
+ builder.category("Game Stats")
+ builder.append("FPS", Minecraft.getDebugFPS().toString())
+ builder.append("Loaded Mods", Loader.instance().activeModList.size)
+ builder.append("Forge", ForgeVersion.getVersion())
+ builder.append("Optifine", if (FMLClientHandler.instance().hasOptifine()) "TRUE" else "FALSE")
+ builder.category("Neu Settings")
+ builder.append("API Key", if (NotEnoughUpdates.INSTANCE.config.apiData.apiKey.isEmpty()) "FALSE" else "TRUE")
+ builder.append("On SkyBlock", if (NotEnoughUpdates.INSTANCE.hasSkyblockScoreboard()) "TRUE" else "FALSE")
+ builder.append(
+ "Mod Version",
+ Loader.instance().indexedModList[NotEnoughUpdates.MODID]!!.displayVersion
+ )
+ builder.append("SB Profile", SBInfo.getInstance().currentProfile)
+ builder.append("Has Advanced Tab", if (SBInfo.getInstance().hasNewTab) "TRUE" else "FALSE")
+ builder.category("Repo Stats")
+ builder.append("Last Commit", NotEnoughUpdates.INSTANCE.manager.latestRepoCommit)
+ builder.append("Loaded Items", NotEnoughUpdates.INSTANCE.manager.itemInformation.size.toString())
+ }
+
+ private fun appendModList(builder: DiscordMarkdownBuilder): DiscordMarkdownBuilder {
+ builder.category("Mods Loaded")
+ Loader.instance().activeModList.forEach {
+ builder.append(it.name, "${it.source} (${it.displayVersion})")
+ }
+ return builder
+ }
+
+ fun CommandContext<ICommandSender>.clipboardAndSendMessage(data: String?) {
+ if (data == null) {
+ reply("${DARK_RED}Error occurred trying to perform command.")
+ return
+ }
+ try {
+ val clipboard = StringSelection(data)
+ Toolkit.getDefaultToolkit().systemClipboard.setContents(clipboard, null)
+ reply("${GREEN}Dev info copied to clipboard.")
+ } catch (ignored: Exception) {
+ reply("${DARK_RED}Could not copy to clipboard.")
+ }
+ }
+}
diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/PackDevCommand.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/PackDevCommand.kt
new file mode 100644
index 00000000..fceacfab
--- /dev/null
+++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/commands/dev/PackDevCommand.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUp