From 2946914101799677753774d8767b4f182323f8b5 Mon Sep 17 00:00:00 2001 From: illyum Date: Tue, 22 Apr 2025 09:22:46 -0600 Subject: [PATCH] feat: add rewrite --- README.md | 7 +- build.gradle.kts | 50 +-- docs/CLIENT_COMMAND_BUILDER.md | 131 +++++++ gradle.properties | 10 +- settings.gradle.kts | 2 +- .../kotlin/com/github/itzilly/sbt/Keybinds.kt | 26 ++ .../com/github/itzilly/sbt/SkyBlockTweaks.kt | 62 ++++ .../github/itzilly/sbt/command/Annotations.kt | 10 + .../github/itzilly/sbt/command/BaseCommand.kt | 31 ++ .../itzilly/sbt/command/ClientCommands.kt | 48 +++ .../itzilly/sbt/command/CommandContext.kt | 54 +++ .../itzilly/sbt/command/ModCommandRegistry.kt | 66 ++++ .../com/github/itzilly/sbt/data/RouteNode.kt | 17 + .../kotlin/com/github/itzilly/sbt/data/Vec.kt | 4 + .../itzilly/sbt/features/GrottoFinder.kt | 338 ++++++++++++++++++ .../itzilly/sbt/features/RouteBuilder.kt | 121 +++++++ .../itzilly/sbt/mixin/MixinGuiMainMenu.java | 16 - .../sbt/render/BlockOutlineRenderer.kt | 178 +++++++++ .../itzilly/sbt/render/RenderPrimitives.kt | 318 ++++++++++++++++ .../com/github/itzilly/sbt/util/Chatterbox.kt | 41 +++ .../itzilly/sbt/util/DelayedFunction.kt | 24 ++ .../github/itzilly/sbt/util/SimpleChatMsg.kt | 146 ++++++++ .../com/github/itzilly/sbt/util/WorldUtils.kt | 19 + src/main/resources/assets/sbt/lang/en_US.lang | 11 +- src/main/resources/mcmod.info | 8 +- 25 files changed, 1684 insertions(+), 54 deletions(-) create mode 100644 docs/CLIENT_COMMAND_BUILDER.md create mode 100644 src/main/kotlin/com/github/itzilly/sbt/Keybinds.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/SkyBlockTweaks.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/command/Annotations.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/command/BaseCommand.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/command/ClientCommands.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/command/CommandContext.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/command/ModCommandRegistry.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/data/RouteNode.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/data/Vec.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/features/GrottoFinder.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/features/RouteBuilder.kt delete mode 100644 src/main/kotlin/com/github/itzilly/sbt/mixin/MixinGuiMainMenu.java create mode 100644 src/main/kotlin/com/github/itzilly/sbt/render/BlockOutlineRenderer.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/render/RenderPrimitives.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/util/Chatterbox.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/util/DelayedFunction.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/util/SimpleChatMsg.kt create mode 100644 src/main/kotlin/com/github/itzilly/sbt/util/WorldUtils.kt diff --git a/README.md b/README.md index 4735d2e..9d6b0d7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # SkyBlockTweaks -My QOL mod for Hypixel Skyblock \ No newline at end of file +My QOL mod for Hypixel Skyblock + +## TODO (Asin these WILL be implemented): +- Add route builder history GUI/Command or something +- Make Route Builder export to Skytils/CrystalHollowWaypoints/Coleweight +- Optimize Block Outline Rendering \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 00586ef..ba3a6ba 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import org.apache.commons.lang3.SystemUtils +import java.util.Properties plugins { idea @@ -9,25 +10,24 @@ plugins { kotlin("jvm") version "2.0.0" } -//Constants: - val baseGroup: String by project val mcVersion: String by project -val version: String by project -val mixinGroup = "$baseGroup.mixin" +val versionBase: String by project +val buildNumber: String by project val modid: String by project +val mixinGroup = "$baseGroup.mixin" + +group = baseGroup +version = "alpha_$versionBase-b$buildNumber" -// Toolchains: java { toolchain.languageVersion.set(JavaLanguageVersion.of(8)) } -// Minecraft configuration: loom { log4jConfigs.from(file("log4j2.xml")) launchConfigs { "client" { - // If you don't want mixins, remove these lines property("mixin.debug", "true") arg("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") } @@ -35,7 +35,6 @@ loom { runConfigs { "client" { if (SystemUtils.IS_OS_MAC_OSX) { - // This argument causes a crash on macOS vmArgs.remove("-XstartOnFirstThread") } } @@ -43,10 +42,8 @@ loom { } forge { pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) - // If you don't want mixins, remove this lines mixinConfig("mixins.$modid.json") } - // If you don't want mixins, remove these lines mixin { defaultRefmapName.set("mixins.$modid.refmap.json") } @@ -62,12 +59,9 @@ sourceSets.main { kotlin.destinationDirectory.set(java.destinationDirectory) } -// Dependencies: - repositories { mavenCentral() maven("https://repo.spongepowered.org/maven/") - // If you don't want to log in with your real minecraft account, remove this line maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") } @@ -82,30 +76,24 @@ dependencies { shadowImpl(kotlin("stdlib-jdk8")) - // If you don't want mixins, remove these lines shadowImpl("org.spongepowered:mixin:0.7.11-SNAPSHOT") { isTransitive = false } annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT") - // If you don't want to log in with your real minecraft account, remove this line runtimeOnly("me.djtheredstoner:DevAuth-forge-legacy:1.2.1") - } -// Tasks: - tasks.withType(JavaCompile::class) { options.encoding = "UTF-8" } tasks.withType(org.gradle.jvm.tasks.Jar::class) { archiveBaseName.set(modid) + archiveVersion.set("$version") manifest.attributes.run { this["FMLCorePluginContainsFMLMod"] = "true" this["ForceLoadAsMod"] = "true" - - // If you don't want mixins, remove these lines this["TweakClass"] = "org.spongepowered.asm.launch.MixinTweaker" this["MixinConfigs"] = "mixins.$modid.json" } @@ -124,7 +112,6 @@ tasks.processResources { rename("(.+_at.cfg)", "META-INF/$1") } - val remapJar by tasks.named("remapJar") { archiveClassifier.set("") from(tasks.shadowJar) @@ -145,10 +132,25 @@ tasks.shadowJar { println("Copying dependencies into mod: ${it.files}") } } - - // If you want to include other dependencies and shadow them, you can relocate them in here fun relocate(name: String) = relocate(name, "$baseGroup.deps.$name") } -tasks.assemble.get().dependsOn(tasks.remapJar) +tasks.assemble { + dependsOn(tasks.remapJar) + dependsOn("incrementBuildNumber") +} +tasks.register("incrementBuildNumber") { + doLast { + val propsFile = file("gradle.properties") + val props = Properties() + props.load(propsFile.inputStream()) + + val current = props.getProperty("buildNumber")?.toIntOrNull() ?: 1 + val next = current + 1 + props.setProperty("buildNumber", next.toString()) + props.store(propsFile.outputStream(), null) + + println("🔢 Build number incremented to $next") + } +} diff --git a/docs/CLIENT_COMMAND_BUILDER.md b/docs/CLIENT_COMMAND_BUILDER.md new file mode 100644 index 0000000..f20e0e7 --- /dev/null +++ b/docs/CLIENT_COMMAND_BUILDER.md @@ -0,0 +1,131 @@ +# Client Command Framework +I'm using a lightweight Kotlin DSL, annotation-based command system for making client side commands. + +## Defining Commands + +All commands are defined as Kotlin functions annotated with `@SubCommand` in `ClientCommands.kt`. + +### Example: + +```kotlin +@SubCommand( + name = "test", + description = "Runs a test command" +) +fun test(ctx: CommandContext) { + ctx.send("Test command executed!") +} +``` + +### Subcommand Options: +- `name`: command name (required) +- `usage`: shows usage in help menu +- `description`: description shown in help +- `aliases`: optional aliases + +### CommandContext helpers: +- `ctx.arg(index: Int)`: get argument (nullable) +- `ctx.require(index: Int, errorMsg: String)`: get required argument (throws error if missing) +- `ctx.send(msg: String)`: send message to client player + +## Help System +- `/sbt` shows all available subcommands +- `/sbt help ` shows usage & description for a subcommand + +## Tab Completion +- Tab completion works for first argument (subcommands) + + +### Example: + +```kotlin +@SubCommand( + name = "test", + description = "Runs a test command" +) +fun test(ctx: CommandContext) { + ctx.send("Test command executed!") +} +``` + +### Subcommand Options: +- `name`: command name (required) +- `usage`: shows usage in help menu +- `description`: description shown in help +- `aliases`: optional aliases + +### CommandContext helpers: +- `ctx.arg(index: Int)`: get argument (nullable) +- `ctx.require(index: Int, errorMsg: String)`: get required argument (throws error if missing) +- `ctx.send(msg: String)`: send message to client player + +## Help System +- `/sbt` shows all available subcommands +- `/sbt help ` shows usage & description for a subcommand + +## Tab Completion +- Tab completion works for first argument (subcommands) + +## Example Commands + +```kotlin +@SubCommand( + name = "autogrotto", + usage = "", + description = "Manages the AutoGrotto feature" +) +fun autogrotto(ctx: CommandContext) { + when (ctx.require(0, "Provide status, enable, or disable")) { + "status" -> ctx.send("Autogrotto is enabled") + "enable" -> ctx.send("Autogrotto enabled") + "disable" -> ctx.send("Autogrotto disabled") + else -> ctx.send("Invalid usage. Try /sbt help autogrotto") + } +} +``` + +## How to Add a New Command +1. Go to `ClientCommands.kt` +2. Define a function +3. Annotate with `@SubCommand` +4. Use `ctx` to get args and send messages + +That's it! + +## Using CommandContext Helpers + +```kotlin +val playerName = ctx.player?.name // Nullable: client-side player +val firstArg = ctx.arg(0) // Safe nullable accessor +val secondArgRequired = ctx.require(1, "Missing second argument") // Throws if missing +ctx.send("You said: $firstArg and $secondArgRequired") // Send chat to player +``` + +## Calling Functions From Another Class + +Create a utility or manager class like: + +```kotlin +object MyFeatureManager { + fun toggleFeature(enabled: Boolean) { + // logic here + } +} +``` + +Then call it from a command: + +```kotlin +@SubCommand(name = "feature", usage = "", description = "Toggles a feature") +fun feature(ctx: CommandContext) { + val toggle = ctx.require(0, "Provide a toggle option") + when (toggle.lowercase()) { + "on", "enable" -> // same action + "off", "disable" -> // another action + else -> { + ctx.send("Invalid argument '$toggle'. Usage:") + ctx.send("/sbt autogrotto ") + } + } +} +``` diff --git a/gradle.properties b/gradle.properties index 6d9ab6f..51ce156 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,8 @@ +#Tue Apr 22 09:20:13 MDT 2025 +versionBase=2.0.0 loom.platform=forge +baseGroup=com.github.itzilly.sbt +mcVersion=1.8.9 org.gradle.jvmargs=-Xmx2g -baseGroup = com.github.itzilly.sbt -mcVersion = 1.8.9 -modid = sbt -version = 1.0.0 +buildNumber=214 +modid=sbt diff --git a/settings.gradle.kts b/settings.gradle.kts index 89fa02f..f89e407 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { maven("https://maven.fabricmc.net") maven("https://maven.minecraftforge.net/") maven("https://repo.spongepowered.org/maven/") - maven("https://repo.sk1er.club/repository/maven-releases/") + maven("https://repo.essential.gg/repository/maven-releases/") } resolutionStrategy { eachPlugin { diff --git a/src/main/kotlin/com/github/itzilly/sbt/Keybinds.kt b/src/main/kotlin/com/github/itzilly/sbt/Keybinds.kt new file mode 100644 index 0000000..e0a2837 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/Keybinds.kt @@ -0,0 +1,26 @@ +package com.github.itzilly.sbt + +import net.minecraft.client.settings.KeyBinding +import net.minecraftforge.fml.client.registry.ClientRegistry +import org.lwjgl.input.Keyboard + + +object Keybinds { + // Grotto Finder + var searchGrotto: KeyBinding = KeyBinding("key.searchGrotto", Keyboard.KEY_Y, "key.categories.sbt") + var clearGrotto: KeyBinding = KeyBinding("key.clearGrotto", Keyboard.KEY_X, "key.categories.sbt") + + // Route Builder + var addRouteNode: KeyBinding = KeyBinding("key.addRouteNode", Keyboard.KEY_ADD, "key.categories.sbt") + var removeLastNode: KeyBinding = KeyBinding("key.removeLastNode", Keyboard.KEY_MINUS, "key.categories.sbt") + var finishRoute: KeyBinding = KeyBinding("key.finishRoute", Keyboard.KEY_F4, "key.categories.sbt") + + fun register() { + ClientRegistry.registerKeyBinding(searchGrotto) + ClientRegistry.registerKeyBinding(clearGrotto) + + ClientRegistry.registerKeyBinding(addRouteNode) + ClientRegistry.registerKeyBinding(removeLastNode) + ClientRegistry.registerKeyBinding(finishRoute) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/SkyBlockTweaks.kt b/src/main/kotlin/com/github/itzilly/sbt/SkyBlockTweaks.kt new file mode 100644 index 0000000..35b23cc --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/SkyBlockTweaks.kt @@ -0,0 +1,62 @@ +package com.github.itzilly.sbt + + +import com.github.itzilly.sbt.command.ClientCommands +import com.github.itzilly.sbt.command.ModCommandRegistry +import com.github.itzilly.sbt.command.registerBaseCommand +import com.github.itzilly.sbt.features.GrottoFinder +import com.github.itzilly.sbt.features.RouteBuilder +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.common.config.Configuration +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLInitializationEvent +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent +import java.io.File + +@Mod(modid = "sbt", useMetadata = true, clientSideOnly = true) +class SkyBlockTweaks { + init { + INSTANCE = this + } + + companion object { + lateinit var INSTANCE: SkyBlockTweaks + lateinit var config: Configuration + } + + @Mod.EventHandler + fun preInit(event: FMLPreInitializationEvent) { + val directory = event.modConfigurationDirectory + config = Configuration(File(directory.path, "sbt.cfg")) + loadConfig() + } + + @Mod.EventHandler + fun init(event: FMLInitializationEvent) { + Keybinds.register() + registerEventListeners() + + ModCommandRegistry.register(ClientCommands) + registerBaseCommand() + } + + private fun loadConfig() { + config.load() + + if (config.hasChanged()) { + config.save() + } + } + + fun syncConfig() { + loadConfig() + if (config.hasChanged()) { + config.save() + } + } + + private fun registerEventListeners() { + MinecraftForge.EVENT_BUS.register(RouteBuilder) + MinecraftForge.EVENT_BUS.register(GrottoFinder) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/command/Annotations.kt b/src/main/kotlin/com/github/itzilly/sbt/command/Annotations.kt new file mode 100644 index 0000000..cdb7e9e --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/command/Annotations.kt @@ -0,0 +1,10 @@ +package com.github.itzilly.sbt.command + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class SubCommand( + val name: String, + val usage: String = "", + val description: String = "", + val aliases: Array = [] +) \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/command/BaseCommand.kt b/src/main/kotlin/com/github/itzilly/sbt/command/BaseCommand.kt new file mode 100644 index 0000000..160faf7 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/command/BaseCommand.kt @@ -0,0 +1,31 @@ +package com.github.itzilly.sbt.command + +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraft.util.BlockPos +import net.minecraftforge.client.ClientCommandHandler + +fun registerBaseCommand() { + ClientCommandHandler.instance.registerCommand(object : CommandBase() { + override fun getCommandName() = "sbt" + override fun getCommandUsage(sender: ICommandSender) = "/sbt " + + override fun processCommand(sender: ICommandSender, args: Array) { + ModCommandRegistry.dispatch(CommandContext(sender, args)) + } + + override fun addTabCompletionOptions( + sender: ICommandSender, + args: Array, + pos: BlockPos? + ): List? { + return if (args.size == 1) { + ModCommandRegistry.subcommands.keys.toList().distinct().filter { it.startsWith(args[0]) } + } else null + } + + override fun canCommandSenderUseCommand(sender: ICommandSender): Boolean { + return true + } + }) +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/command/ClientCommands.kt b/src/main/kotlin/com/github/itzilly/sbt/command/ClientCommands.kt new file mode 100644 index 0000000..8c7aeca --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/command/ClientCommands.kt @@ -0,0 +1,48 @@ +package com.github.itzilly.sbt.command + +import com.github.itzilly.sbt.features.GrottoFinder +import com.github.itzilly.sbt.util.Chatterbox +import com.github.itzilly.sbt.util.SimpleChatMsg + +object ClientCommands { + + @SubCommand( + name = "autogrotto", + usage = "", + description = "Manages the AutoGrotto feature" + ) + fun autogrotto(ctx: CommandContext) { + val action = ctx.arg(0)?.lowercase() + + when (action) { + "status" -> { + val isEnabled = GrottoFinder.isAutoDetectEnabled() + Chatterbox.say("Autogrotto is currently ${if (isEnabled) "enabled" else "disabled"}") + } + + "enable", "on" -> { + GrottoFinder.enableAutoDetect() + Chatterbox.say("Autogrotto has been enabled") + } + + "disable", "off" -> { + GrottoFinder.disableAutoDetect() + Chatterbox.say("Autogrotto has been disabled") + } + + else -> { + Chatterbox.say(SimpleChatMsg("Invalid argument! Usage:").red()) + Chatterbox.say("/sbt autogrotto ") + } + } + } + + @SubCommand( + name = "reveal_grotto", + usage = "", + description = "Reveal Grotto" + ) + fun reveal_grotto(ctx: CommandContext) { + GrottoFinder.revealDetectedJasperBlocks() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/command/CommandContext.kt b/src/main/kotlin/com/github/itzilly/sbt/command/CommandContext.kt new file mode 100644 index 0000000..c0aa155 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/command/CommandContext.kt @@ -0,0 +1,54 @@ +package com.github.itzilly.sbt.command + +import net.minecraft.client.Minecraft +import net.minecraft.command.ICommandSender +import net.minecraft.util.ChatComponentText + +/** + * Wrapper for command execution context. + * + * Provides access to the command sender, arguments, and helper methods. + * + * @property sender The ICommandSender executing the command. + * @property args The raw array of command arguments. + */ +class CommandContext( + val sender: ICommandSender, + var args: Array +) { + + /** + * The client-side player instance. + * Can be null in rare cases (e.g., not in-game). + */ + val player get() = Minecraft.getMinecraft().thePlayer + + /** + * Sends a chat message to the player. + * + * @param msg The message to send. + */ + fun send(msg: String) { + player?.addChatMessage(ChatComponentText(msg)) + } + + /** + * Retrieves an argument at the given index, or null if not present. + * + * @param index The argument index. + * @return The argument or null. + */ + fun arg(index: Int): String? = args.getOrNull(index) + + /** + * Retrieves an argument and throws if it's missing. + * + * @param index The argument index. + * @param error Error message to show if argument is missing. + * @return The argument string. + * @throws IllegalArgumentException if missing. + */ + fun require(index: Int, error: String): String { + return args.getOrNull(index) ?: throw IllegalArgumentException(error) + } +} diff --git a/src/main/kotlin/com/github/itzilly/sbt/command/ModCommandRegistry.kt b/src/main/kotlin/com/github/itzilly/sbt/command/ModCommandRegistry.kt new file mode 100644 index 0000000..6dc32e4 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/command/ModCommandRegistry.kt @@ -0,0 +1,66 @@ +package com.github.itzilly.sbt.command + +object ModCommandRegistry { + val subcommands = mutableMapOf() + + fun register(target: Any) { + for (method in target::class.java.declaredMethods) { + val annotation = method.getAnnotation(SubCommand::class.java) ?: continue + val entry = CommandEntry(annotation, method, target) + subcommands[annotation.name.lowercase()] = entry + annotation.aliases.forEach { + subcommands[it.lowercase()] = entry + } + } + } + + fun dispatch(context: CommandContext) { + val sub = context.arg(0)?.lowercase() + val subArgs = context.args.drop(1).toTypedArray() + + if (sub == null || sub == "help") { + showHelp(context, subArgs.firstOrNull()) + return + } + + val cmd = subcommands[sub] + if (cmd != null) { + context.args = subArgs + cmd.invoke(context) + } else { + context.send("Unknown subcommand '$sub'. Use /sbt help.") + } + } + + private fun showHelp(ctx: CommandContext, target: String?) { + if (target != null) { + val cmd = subcommands[target.lowercase()] + if (cmd != null) { + ctx.send("Usage: /sbt ${cmd.annotation.name} ${cmd.annotation.usage}") + ctx.send(cmd.annotation.description) + return + } + ctx.send("Unknown command '$target'") + return + } + + ctx.send("Available /sbt commands:") + subcommands.values.toSet().forEach { + ctx.send("- ${it.annotation.name} ${it.annotation.usage} : ${it.annotation.description}") + } + } + + class CommandEntry( + val annotation: SubCommand, + val method: java.lang.reflect.Method, + val target: Any + ) { + fun invoke(ctx: CommandContext) { + try { + method.invoke(target, ctx) + } catch (e: Exception) { + ctx.send("§cError: ${e.cause?.message ?: e.message}") + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/data/RouteNode.kt b/src/main/kotlin/com/github/itzilly/sbt/data/RouteNode.kt new file mode 100644 index 0000000..b2d99aa --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/data/RouteNode.kt @@ -0,0 +1,17 @@ +package com.github.itzilly.sbt.data + +data class RouteNode( + val x: Int, + val y: Int, + val z: Int, + + val r: UByte, + val g: UByte, + val b: UByte, + + val options: NodeOptions +) + +data class NodeOptions( + val name: String +) \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/data/Vec.kt b/src/main/kotlin/com/github/itzilly/sbt/data/Vec.kt new file mode 100644 index 0000000..81c6e7e --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/data/Vec.kt @@ -0,0 +1,4 @@ +package com.github.itzilly.sbt.data + +data class Vecd3(val x:Double, val y:Double, val z:Double) +data class Vecf3(val x:Float, val y:Float, val z:Float) \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/features/GrottoFinder.kt b/src/main/kotlin/com/github/itzilly/sbt/features/GrottoFinder.kt new file mode 100644 index 0000000..e607bb2 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/features/GrottoFinder.kt @@ -0,0 +1,338 @@ +package com.github.itzilly.sbt.features + +import com.github.itzilly.sbt.Keybinds +import com.github.itzilly.sbt.render.BlockOutlineRenderer +import com.github.itzilly.sbt.util.Chatterbox +import com.github.itzilly.sbt.util.DelayedFunction +import com.github.itzilly.sbt.util.SimpleChatMsg +import com.github.itzilly.sbt.util.getMiniServerIdFromScoreboard +import net.minecraft.block.Block +import net.minecraft.client.Minecraft +import net.minecraft.init.Blocks +import net.minecraft.util.BlockPos +import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraft.util.EnumChatFormatting +import net.minecraft.world.World +import net.minecraft.world.chunk.Chunk +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.event.entity.EntityJoinWorldEvent +import net.minecraftforge.event.world.ChunkEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.InputEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +object GrottoFinder { + private var isRunningAutoDetect: Boolean = true + private var autoDetectEnabled: Boolean = false + private val scannedChunks = mutableMapOf, Long>() + private val pendingJasperBlocks = mutableListOf() + private var hasLocatedGrotto = false + private const val SCAN_COOLDOWN_MS = 5 * 60 * 1100 // 5 minutes (+ a little to account for lag) + + // World Bounds 824, 189, 824 201, 30, 201 + private val minBounds = BlockPos(201, 30, 201) // Max area of the Crystal Hollows + private val maxBounds = BlockPos(824, 189, 824) // Max area of the Crystal Hollows + private val targetBlock: Block = Blocks.stained_glass + private val targetMeta: Int = 2 // Magenta stained glass + + private var lastWorldLoadTime = 0L + private var lastMiniServerId: String? = null + + private var renderBlockList: ArrayList = ArrayList() + private var jasperCrystalsList: ArrayList = ArrayList() + + fun enableAutoDetect() { + autoDetectEnabled = true + } + + fun disableAutoDetect() { + autoDetectEnabled = false + } + + fun isAutoDetectEnabled(): Boolean { + return autoDetectEnabled + } + + private fun clear() { + renderBlockList.clear() + } + + private fun magentaNodeExists(x: Int, y: Int, z: Int): Boolean { + return renderBlockList.any { it.x == x && it.y == y && it.z == z } + } + + @SubscribeEvent + fun onKeyInput(event: InputEvent.KeyInputEvent) { + if (Keybinds.searchGrotto.isPressed) { + Chatterbox.say("Checking chunks for grotto...") + checkWorldForMagentaStainedGlassAsync(Minecraft.getMinecraft().theWorld) + renderBlockList.addAll(jasperCrystalsList) + } + + if (Keybinds.clearGrotto.isPressed) { + renderBlockList.clear() + } + } + + @Deprecated("Use async instead") + private fun checkWorldForMagentaStainedGlassSync(world: World) { + for (chunkX in minBounds.x / 16..maxBounds.x / 16) { + for (chunkZ in minBounds.z / 16..maxBounds.z / 16) { + val chunk = world.getChunkFromChunkCoords(chunkX, chunkZ) + + // Iterate through the chunk's blocks within the world bounds + for (x in 0..15) { + for (y in minBounds.y..maxBounds.y) { + for (z in 0..15) { + val worldX = chunk.xPosition * 16 + x + val worldZ = chunk.zPosition * 16 + z + + // Ensure the block is within the specified bounds + if (worldX in minBounds.x..maxBounds.x && worldZ in minBounds.z..maxBounds.z) { + val blockPos = BlockPos(worldX, y, worldZ) + val blockState = chunk.getBlockState(blockPos) + val block = blockState.block + val meta = block.getMetaFromState(blockState) + + if (block === targetBlock && meta == targetMeta) { + if (!magentaNodeExists(blockPos.x, blockPos.y, blockPos.z)) { + renderBlockList.add(BlockPos(blockPos.x, blockPos.y, blockPos.z)) + } + } + } + } + } + } + } + } + if (renderBlockList.isNotEmpty()) { + val pos = renderBlockList[0] + val msg = SimpleChatMsg( + "Found Jasper near (%d, %d, %d)".format( + pos.x, + pos.y, + pos.z + ) + ).light_purple() + Chatterbox.say(msg) + } + } + + private fun checkWorldForMagentaStainedGlassAsync(world: World) { + Thread { + val foundBlocks = ArrayList() + for (chunkX in minBounds.x / 16..maxBounds.x / 16) { + for (chunkZ in minBounds.z / 16..maxBounds.z / 16) { + val chunk = world.getChunkFromChunkCoords(chunkX, chunkZ) + + for (x in 0..15) { + for (y in minBounds.y..maxBounds.y) { + for (z in 0..15) { + val worldX = chunk.xPosition * 16 + x + val worldZ = chunk.zPosition * 16 + z + + if (worldX in minBounds.x..maxBounds.x && worldZ in minBounds.z..maxBounds.z) { + val blockPos = BlockPos(worldX, y, worldZ) + val blockState = chunk.getBlockState(blockPos) + val block = blockState.block + val meta = block.getMetaFromState(blockState) + + if (block === targetBlock && meta == targetMeta) { + foundBlocks.add(blockPos) + } + } + } + } + } + } + } + + Minecraft.getMinecraft().addScheduledTask { + clear() + renderBlockList.addAll(foundBlocks) + + if (renderBlockList.isNotEmpty()) { + val pos = renderBlockList[0] + val msg = SimpleChatMsg( + "Found Jasper near (%d, %d, %d)".format( + pos.x, + pos.y, + pos.z + ) + ).light_purple() + Chatterbox.say(msg) + } else { + Chatterbox.say("No Jasper found.") + } + } + }.start() + } + + @SubscribeEvent + fun renderBlockOverlay(event: RenderWorldLastEvent) { + val player = Minecraft.getMinecraft().thePlayer + + for (glass: BlockPos in renderBlockList) { + val pos = BlockPos(glass.x, glass.y, glass.z) + BlockOutlineRenderer.drawBlockOutline(player, pos, event.partialTicks) + BlockOutlineRenderer.drawBlockLine(pos, event.partialTicks) + } + } + + private fun isInCrystalHollowsFromTab(): Boolean { + val infoList = Minecraft.getMinecraft().thePlayer?.sendQueue?.playerInfoMap ?: return false + + val lines = infoList + .mapNotNull { it.displayName?.unformattedText } + .map { it.trim() } + .filter { it.isNotBlank() } + + for (i in 0 until lines.size - 1) { + if (lines[i].equals("Info", ignoreCase = true) && + lines[i + 1].contains("Area: Crystal Hollows", ignoreCase = true) + ) { + return true + } + } + + return false + } + +// @SubscribeEvent +// fun onEntityJoinWorldEvent(event: EntityJoinWorldEvent) { +// val mc = Minecraft.getMinecraft() +// val player = mc.thePlayer +// val TWO_SECONDS = 2000 +// +// if (event.entity === player && event.world.isRemote) { +// val now = System.currentTimeMillis() +// if (now - lastWorldLoadTime < TWO_SECONDS) return +// lastWorldLoadTime = now +// +// DelayedFunction({ +// isRunningAutoDetect = isInCrystalHollowsFromTab() +// if (isRunningAutoDetect) { +// if (pendingJasperBlocks.isNotEmpty()) { +// jasperCrystalsList.addAll(pendingJasperBlocks) +// pendingJasperBlocks.clear() +// showRevealMessage() +// } +// } +// }, delayTicks = 100) +// } +// } + +// @SubscribeEvent +// fun onClientTick(event: TickEvent.ClientTickEvent) { +// if (!autoDetectEnabled || event.phase != TickEvent.Phase.END) return +// +// val currentMiniId = getMiniServerIdFromScoreboard() ?: return +// if (currentMiniId != lastMiniServerId) { +// if (lastMiniServerId != null) { +// // TODO: fix +//// isRunningAutoDetect = false +// renderBlockList.clear() +// jasperCrystalsList.clear() +// pendingJasperBlocks.clear() +// hasLocatedGrotto = false +// } +// lastMiniServerId = currentMiniId +// } +// } + +// @SubscribeEvent +// fun onChunkLoad(event: ChunkEvent.Load) { +// if (!autoDetectEnabled) return +// +// val chunk = event.chunk +// val key = Pair(chunk.xPosition, chunk.zPosition) +// val now = System.currentTimeMillis() +// +// if (now - (scannedChunks[key] ?: 0) < SCAN_COOLDOWN_MS) return +// scannedChunks[key] = now +// +// scanChunkForJasper(chunk) +// } + + private fun scanChunkForJasper(chunk: Chunk) { + Thread { + val found = mutableListOf() + + val baseX = chunk.xPosition * 16 + val baseZ = chunk.zPosition * 16 + + for (x in 0..15) { + for (y in minBounds.y..maxBounds.y) { + for (z in 0..15) { + val pos = BlockPos(baseX + x, y, baseZ + z) + val blockState = chunk.getBlockState(pos) + val block = blockState.block + val meta = block.getMetaFromState(blockState) + + if (block == targetBlock && meta == targetMeta) { + found.add(pos) + } + } + } + } + + if (found.count() != 1) { + if (isRunningAutoDetect) { + jasperCrystalsList.addAll(found) + if (!hasLocatedGrotto) { + showRevealMessage() + hasLocatedGrotto = true + } + } else { + pendingJasperBlocks.addAll(found) + } + + } + + Chatterbox.say("Scanned Chunk, found: ${found.count()}") + }.start() + } + + private fun showRevealMessage() { + Chatterbox.say(SimpleChatMsg("- - - - - - - - - - - - - - - - - - -").light_purple()) + val clickMsg = ChatComponentText("§dDetected Fairy Grotto! §7[Click to reveal]").apply { + chatStyle = ChatStyle().apply { + color = EnumChatFormatting.LIGHT_PURPLE + underlined = true + chatClickEvent = net.minecraft.event.ClickEvent( + net.minecraft.event.ClickEvent.Action.RUN_COMMAND, + "/sbt reveal_grotto" + ) + chatHoverEvent = net.minecraft.event.HoverEvent( + net.minecraft.event.HoverEvent.Action.SHOW_TEXT, + ChatComponentText("§7Click to show block outlines.") + ) + } + } + Chatterbox.say(clickMsg) + Chatterbox.say(SimpleChatMsg("- - - - - - - - - - - - - - - - - - -").light_purple()) + } + + fun revealDetectedJasperBlocks() { + jasperCrystalsList.addAll(pendingJasperBlocks) + if (jasperCrystalsList.isEmpty()) { + Chatterbox.say(SimpleChatMsg("[Grotto] No Jasper found!").light_purple()) + return + } + + renderBlockList.clear() + renderBlockList.addAll(jasperCrystalsList) + Chatterbox.say(SimpleChatMsg("[Grotto] Revealed detected Fairy Grotto blocks").light_purple()) + + val pos = renderBlockList[0] + val msg = SimpleChatMsg( + "Found Jasper near (%d, %d, %d)".format( + pos.x, + pos.y, + pos.z + ) + ).light_purple() + Chatterbox.say(msg) + } +} diff --git a/src/main/kotlin/com/github/itzilly/sbt/features/RouteBuilder.kt b/src/main/kotlin/com/github/itzilly/sbt/features/RouteBuilder.kt new file mode 100644 index 0000000..85e1bc5 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/features/RouteBuilder.kt @@ -0,0 +1,121 @@ +package com.github.itzilly.sbt.features + +import com.github.itzilly.sbt.Keybinds +import com.github.itzilly.sbt.data.NodeOptions +import com.github.itzilly.sbt.data.RouteNode +import com.github.itzilly.sbt.render.BlockOutlineRenderer +import com.github.itzilly.sbt.util.Chatterbox +import com.github.itzilly.sbt.util.SimpleChatMsg +import com.google.gson.Gson +import net.minecraft.client.Minecraft +import net.minecraft.util.BlockPos +import net.minecraftforge.client.event.RenderWorldLastEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.InputEvent +import java.awt.Toolkit +import java.awt.datatransfer.Clipboard +import java.awt.datatransfer.StringSelection +import kotlin.math.round + +object RouteBuilder { + private const val OFFSET: Float = 0.5f; + private const val MAX_HISTORY = 5 + + private var nodes: ArrayList = ArrayList() + private val history = ArrayDeque>() + + private fun nodeExists(x: Int, y: Int, z: Int): Boolean { + return nodes.any { it.x == x && it.y == y && it.z == z } + } + + private fun addNode(x: Int, y: Int, z: Int) { + nodes.add(RouteNode(x, y, z, 0u, 1u, 0u, NodeOptions("${nodes.size + 1}"))) + } + + private fun addNode(x: Int, y: Int, z: Int, r: UByte, g: UByte, b: UByte) { + nodes.add(RouteNode(x, y, z, r, g, b, NodeOptions("${nodes.size + 1}"))) + } + + @SubscribeEvent + fun onKeyInput(event: InputEvent.KeyInputEvent) { + if (Keybinds.addRouteNode.isPressed) { + addRouteNode() + } + + if (Keybinds.removeLastNode.isPressed) { + removeLastNode() + } + + if (Keybinds.finishRoute.isPressed) { + finishRoute() + } + } + + @SubscribeEvent + fun renderBlockOverlay(event: RenderWorldLastEvent) { + val player = Minecraft.getMinecraft().thePlayer + + for (marker: RouteNode in nodes) { + val pos = BlockPos(marker.x, marker.y, marker.z) + BlockOutlineRenderer.drawBlockOutline(player, pos, event.partialTicks) + BlockOutlineRenderer.drawBlockLine(pos, event.partialTicks) + } + } + + private fun addRouteNode() { + val mc = Minecraft.getMinecraft() + val x = round(mc.thePlayer.posX - OFFSET).toInt() + val y = round(mc.thePlayer.posY).toInt() - 1 // for the block below the player + val z = round(mc.thePlayer.posZ - OFFSET).toInt() + + if (nodeExists(x, y, z)) { + Chatterbox.say(SimpleChatMsg("Node already exists!").red()) + } else { + addNode(x, y, z) + Chatterbox.say("This is a string") + } + } + + private fun removeLastNode() { + val wasRemoved = nodes.removeLastOrNull() + if (wasRemoved == null) { + val msg = SimpleChatMsg("There isn't a node to delete!") + Chatterbox.say(msg.red()) + } else { + val msg = SimpleChatMsg("Removed previous node") + Chatterbox.say(msg.green()) + } + } + + private fun finishRoute() { + if (nodes.size == 0) { + val msg = SimpleChatMsg("There aren't any nodes to copy!") + Chatterbox.say(msg.red()) + return + } + + // TODO: Make a config option to export to coleweight or the other mod (different format) + // Maybe skytils too?? + saveRouteToHistory() + // TODO: Make a way to view the history in case you goof up + + val jsonString = Gson().toJson(nodes) + val msg = SimpleChatMsg("String copied to clipboard!") + setClipboard(jsonString) + Chatterbox.say(msg.green()) + nodes.clear() + } + + private fun setClipboard(s: String) { + val selection = StringSelection(s) + val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard + clipboard.setContents(selection, selection) + } + + private fun saveRouteToHistory() { + history.addLast(nodes.map { it.copy() }) + if (history.size > MAX_HISTORY) { + history.removeFirst() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/mixin/MixinGuiMainMenu.java b/src/main/kotlin/com/github/itzilly/sbt/mixin/MixinGuiMainMenu.java deleted file mode 100644 index 78a39ba..0000000 --- a/src/main/kotlin/com/github/itzilly/sbt/mixin/MixinGuiMainMenu.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.itzilly.sbt.mixin; - -import net.minecraft.client.gui.GuiMainMenu; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(GuiMainMenu.class) -public class MixinGuiMainMenu { - - @Inject(method = "initGui", at = @At("HEAD")) - public void onInitGui(CallbackInfo ci) { - // System.out.println("Hello from Main Menu!"); - } -} diff --git a/src/main/kotlin/com/github/itzilly/sbt/render/BlockOutlineRenderer.kt b/src/main/kotlin/com/github/itzilly/sbt/render/BlockOutlineRenderer.kt new file mode 100644 index 0000000..65aeb94 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/render/BlockOutlineRenderer.kt @@ -0,0 +1,178 @@ +package com.github.itzilly.sbt.render + +import com.github.itzilly.sbt.data.Vecd3 +import net.minecraft.block.Block +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.entity.Entity +import net.minecraft.util.AxisAlignedBB +import net.minecraft.util.BlockPos +import net.minecraft.util.EnumFacing +import org.lwjgl.opengl.GL11 +import java.awt.Color + +object BlockOutlineRenderer { + + fun drawBlockOutline(entity: Entity, blockPos: BlockPos, partialTicks: Float, outlineColor: Int) { + val padding = 0.0020000000949949026 // Magic number DO NOT TOUCH + + val entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + val entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + val entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + + val world = Minecraft.getMinecraft().theWorld + val blockState = world.getBlockState(blockPos) + val block = blockState.block + + val boundingBox = block.getCollisionBoundingBox(world, blockPos, blockState) + ?: AxisAlignedBB( + blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), + blockPos.x + 1.0, blockPos.y + 1.0, blockPos.z + 1.0 + ).expand(padding, padding, padding) + + val r = (outlineColor shr 16 and 0xFF) / 255.0f + val g = (outlineColor shr 8 and 0xFF) / 255.0f + val b = (outlineColor and 0xFF) / 255.0f + val a = (outlineColor shr 24 and 0xFF) / 255.0f + + GL11.glPushMatrix() + GlStateManager.disableTexture2D() + GlStateManager.depthMask(false) + GlStateManager.disableDepth() + GL11.glEnable(GL11.GL_BLEND) + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA) + GL11.glEnable(GL11.GL_LINE_SMOOTH) + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST) + GL11.glLineWidth(2.0f) + GL11.glColor4f(r, g, b, a) + + RenderPrimitives.drawBlock(boundingBox.offset(-entityX, -entityY, -entityZ), outlineColor, outlineColor) + + GL11.glDisable(GL11.GL_LINE_SMOOTH) + GL11.glDisable(GL11.GL_BLEND) + GlStateManager.enableDepth() + GlStateManager.depthMask(true) + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + GL11.glPopMatrix() + } + + fun drawBlockOutline(entity: Entity, blockPos: BlockPos, partialTicks: Float) { + val padding: Double = 0.0020000000949949026 + + val entityX: Double = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + val entityY: Double = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + val entityZ: Double = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + + val outlineStartColor = 0xFFFFFF + val outlineEndColor = 0xFFFFFF + + val world = Minecraft.getMinecraft().theWorld + val blockState = world.getBlockState(blockPos) + val block: Block = blockState.block + + val boundingBox: AxisAlignedBB = block.getCollisionBoundingBox(world, blockPos, blockState) + ?: AxisAlignedBB( + blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), + blockPos.x + 1.0, blockPos.y + 1.0, blockPos.z + 1.0 + ).expand(padding, padding, padding) + + GL11.glPushMatrix() + GlStateManager.disableTexture2D() + GlStateManager.depthMask(false) + GlStateManager.disableDepth() + GL11.glEnable(GL11.GL_LINE_SMOOTH) + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST) + GL11.glLineWidth(2.0f) + GL11.glShadeModel(GL11.GL_SMOOTH) + + RenderPrimitives.drawBlock(boundingBox.offset(-entityX, -entityY, -entityZ), outlineStartColor, outlineEndColor) + + GL11.glLineWidth(2.0f) + GL11.glDisable(GL11.GL_LINE_SMOOTH) + GlStateManager.enableDepth() + GlStateManager.depthMask(true) + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + GL11.glPopMatrix() + } + + fun drawBlockLine(pos: BlockPos, partialTicks: Float) { + val render = Minecraft.getMinecraft().renderViewEntity + val rm = Minecraft.getMinecraft().renderManager + // TODO: Fix this not working with view bobbing enabled + val pos1 = Vecd3(rm.viewerPosX, rm.viewerPosY + render.eyeHeight, rm.viewerPosZ) + + val pos2 = Vecd3( + pos.x.toDouble() + 0.5, // Center of the block + pos.y.toDouble() + 0.5, // Center of the block + pos.z.toDouble() + 0.5 // Center of the block + ) + + val lineColor = Color(0xe310d5) + RenderPrimitives.drawLine(pos1, pos2, lineColor, 2, false, partialTicks) + } + + fun drawClusterOutline(entity: Entity, positions: List, partialTicks: Float, outlineColor: Int) { + val padding = 0.0020000000949949026 // Magic number DO NOT TOUCH + + val entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + val entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + val entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + + val world = Minecraft.getMinecraft().theWorld + + GL11.glPushMatrix() + GlStateManager.disableTexture2D() + GlStateManager.depthMask(false) + GlStateManager.disableDepth() + GL11.glEnable(GL11.GL_BLEND) + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA) + GL11.glEnable(GL11.GL_LINE_SMOOTH) + GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST) + GL11.glLineWidth(2.0f) + + val r = (outlineColor shr 16 and 0xFF) / 255.0f + val g = (outlineColor shr 8 and 0xFF) / 255.0f + val b = (outlineColor and 0xFF) / 255.0f + val a = (outlineColor shr 24 and 0xFF) / 255.0f + + GL11.glColor4f(r, g, b, a) + + for (pos in positions) { + val blockState = world.getBlockState(pos) + val boundingBox = blockState.block.getCollisionBoundingBox(world, pos, blockState) + ?: AxisAlignedBB(pos.x.toDouble(), pos.y.toDouble(), pos.z.toDouble(), pos.x + 1.0, pos.y + 1.0, pos.z + 1.0).expand(padding, padding, padding) + + val offsetBoundingBox = boundingBox.offset(-entityX, -entityY, -entityZ) + + // Only draw exposed faces + if (!positions.contains(pos.add(1, 0, 0))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.EAST, outlineColor) + } + if (!positions.contains(pos.add(-1, 0, 0))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.WEST, outlineColor) + } + if (!positions.contains(pos.add(0, 1, 0))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.UP, outlineColor) + } + if (!positions.contains(pos.add(0, -1, 0))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.DOWN, outlineColor) + } + if (!positions.contains(pos.add(0, 0, 1))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.SOUTH, outlineColor) + } + if (!positions.contains(pos.add(0, 0, -1))) { + RenderPrimitives.drawBlockFace(offsetBoundingBox, EnumFacing.NORTH, outlineColor) + } + } + + GL11.glDisable(GL11.GL_LINE_SMOOTH) + GL11.glDisable(GL11.GL_BLEND) + GlStateManager.enableDepth() + GlStateManager.depthMask(true) + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + GL11.glPopMatrix() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/render/RenderPrimitives.kt b/src/main/kotlin/com/github/itzilly/sbt/render/RenderPrimitives.kt new file mode 100644 index 0000000..3ade281 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/render/RenderPrimitives.kt @@ -0,0 +1,318 @@ +package com.github.itzilly.sbt.render + +import com.github.itzilly.sbt.data.Vecd3 +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.vertex.DefaultVertexFormats +import net.minecraft.util.AxisAlignedBB +import net.minecraft.util.EnumFacing +import org.lwjgl.opengl.GL11 +import java.awt.Color + +object RenderPrimitives { + var TESSELLATOR: Tessellator = Tessellator.getInstance() + var WORLD_RENDERER: WorldRenderer = Tessellator.getInstance().worldRenderer + + fun drawBlock(box: AxisAlignedBB, outlineStartColor: Int, outlineEndColor: Int) { + drawBlockTop(box, Color(outlineStartColor), Color(outlineEndColor)); + drawBlockBottom(box, Color(outlineStartColor), Color(outlineEndColor)); + drawBlockNorth(box, Color(outlineStartColor), Color(outlineEndColor)); + drawBlockEast(box, Color(outlineStartColor), Color(outlineEndColor)); + drawBlockSouth(box, Color(outlineStartColor), Color(outlineEndColor)); + drawBlockWest(box, Color(outlineStartColor), Color(outlineEndColor)); + } + + private fun drawBlockTop(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + private fun drawBlockBottom(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + private fun drawBlockNorth(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + private fun drawBlockEast(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + private fun drawBlockSouth(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + private fun drawBlockWest(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color) { + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawLine(p1: Vecd3, p2: Vecd3, color: Color, lineWidth: Int, depth: Boolean, partialTicks: Float) { + GlStateManager.disableCull() + + val render = Minecraft.getMinecraft().renderViewEntity + val worldRenderer = TESSELLATOR.worldRenderer + + val realX = render.lastTickPosX + (render.posX - render.lastTickPosX) * partialTicks + val realY = render.lastTickPosY + (render.posY - render.lastTickPosY) * partialTicks + val realZ = render.lastTickPosZ + (render.posZ - render.lastTickPosZ) * partialTicks + + + GlStateManager.pushMatrix() + GlStateManager.translate(-realX, -realY, -realZ) + GlStateManager.disableTexture2D() + GlStateManager.enableBlend() + GlStateManager.disableAlpha() + GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0) + GL11.glLineWidth(lineWidth.toFloat()) + if (!depth) { + GL11.glDisable(GL11.GL_DEPTH_TEST) + GlStateManager.depthMask(false) + } + GlStateManager.color(color.red / 255f, color.green / 255f, color.blue / 255f, color.alpha / 255f) + worldRenderer.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION) + worldRenderer.pos(p1.x, p1.y, p1.z).endVertex() + worldRenderer.pos(p2.x, p2.y, p2.z).endVertex() + Tessellator.getInstance().draw() + GlStateManager.translate(realX, realY, realZ) + if (!depth) { + GL11.glEnable(GL11.GL_DEPTH_TEST) + GlStateManager.depthMask(true) + } + GlStateManager.disableBlend() + GlStateManager.enableAlpha() + GlStateManager.enableTexture2D() + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f) + GlStateManager.popMatrix() + GlStateManager.disableLighting() + GlStateManager.enableCull() + } + + fun drawAnimatedBlockTop(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawAnimatedBlockBottom(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawAnimatedBlockNorth(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawAnimatedBlockEast(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawAnimatedBlockSouth(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawAnimatedBlockWest(box: AxisAlignedBB, outlineStartColor: Color, outlineEndColor: Color, time: Float) { + val offsetY = Math.sin(time.toDouble()).toFloat() * (box.maxY - box.minY) / 2.0f + + WORLD_RENDERER.begin(2, DefaultVertexFormats.POSITION_COLOR) + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.minZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY + offsetY, box.maxZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.maxZ) + .color(outlineStartColor.red, outlineStartColor.green, outlineStartColor.blue, outlineStartColor.alpha) + .endVertex() + WORLD_RENDERER.pos(box.minX, box.minY + offsetY, box.minZ) + .color(outlineEndColor.red, outlineEndColor.green, outlineEndColor.blue, outlineEndColor.alpha).endVertex() + TESSELLATOR.draw() + } + + fun drawBlockFace(box: AxisAlignedBB, face: EnumFacing, color: Int) { + WORLD_RENDERER.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION_COLOR) + + val r = (color shr 16 and 0xFF) / 255.0f + val g = (color shr 8 and 0xFF) / 255.0f + val b = (color and 0xFF) / 255.0f + val a = (color shr 24 and 0xFF) / 255.0f + + GL11.glColor4f(r, g, b, a) + + when (face) { + EnumFacing.UP -> { + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + } + EnumFacing.DOWN -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.NORTH -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.SOUTH -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + } + EnumFacing.WEST -> { + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.minX, box.minY, box.minZ).endVertex() + } + EnumFacing.EAST -> { + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.maxZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.maxY, box.minZ).endVertex() + WORLD_RENDERER.pos(box.maxX, box.minY, box.minZ).endVertex() + } + else -> {} + } + + TESSELLATOR.draw() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/util/Chatterbox.kt b/src/main/kotlin/com/github/itzilly/sbt/util/Chatterbox.kt new file mode 100644 index 0000000..9ca202c --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/util/Chatterbox.kt @@ -0,0 +1,41 @@ +package com.github.itzilly.sbt.util + +import net.minecraft.client.Minecraft +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraft.util.EnumChatFormatting +import net.minecraft.util.IChatComponent + +object Chatterbox { + private const val prefixString = "[SBT] " + private val prefixColorStyle = ChatStyle().setColor(EnumChatFormatting.AQUA) + private val prefixComponent = ChatComponentText(prefixString).setChatStyle(prefixColorStyle) + private val player = Minecraft.getMinecraft().thePlayer + + private fun copy(component: IChatComponent): IChatComponent { + val newComponent = ChatComponentText(component.unformattedTextForChat) + newComponent.chatStyle = component.chatStyle.createShallowCopy() + component.siblings.forEach { sibling -> + newComponent.appendSibling(copy(sibling)) + } + return newComponent + } + + fun say(msg: String) { + val messageComponent = ChatComponentText(msg).setChatStyle(ChatStyle().setColor(EnumChatFormatting.RESET)) + player.addChatMessage(prefixComponent.createCopy().appendSibling(messageComponent)) + } + + fun say(component: ChatComponentText) { + player.addChatMessage(copy(prefixComponent).appendSibling(copy(component))) + } + + fun say(component: IChatComponent) { + player.addChatMessage(copy(prefixComponent).appendSibling(copy(component))) + } + + fun say(player: EntityPlayer, component: IChatComponent) { + player.addChatMessage(copy(prefixComponent).appendSibling(copy(component))) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/util/DelayedFunction.kt b/src/main/kotlin/com/github/itzilly/sbt/util/DelayedFunction.kt new file mode 100644 index 0000000..1a79aae --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/util/DelayedFunction.kt @@ -0,0 +1,24 @@ +package com.github.itzilly.sbt.util + +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent + +class DelayedFunction(private val function: () -> Unit, private val delayTicks: Int) { + private var ticksLeft: Int = delayTicks + + init { + MinecraftForge.EVENT_BUS.register(this) + } + + @SubscribeEvent + fun onClientTick(event: TickEvent.ClientTickEvent) { + if (ticksLeft > 0) { + ticksLeft-- + if (ticksLeft == 0) { + function() + MinecraftForge.EVENT_BUS.unregister(this) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/util/SimpleChatMsg.kt b/src/main/kotlin/com/github/itzilly/sbt/util/SimpleChatMsg.kt new file mode 100644 index 0000000..a4f12de --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/util/SimpleChatMsg.kt @@ -0,0 +1,146 @@ +package com.github.itzilly.sbt.util + +import net.minecraft.util.ChatComponentText +import net.minecraft.util.ChatStyle +import net.minecraft.util.EnumChatFormatting.BLACK +import net.minecraft.util.EnumChatFormatting.DARK_BLUE +import net.minecraft.util.EnumChatFormatting.DARK_GREEN +import net.minecraft.util.EnumChatFormatting.DARK_AQUA +import net.minecraft.util.EnumChatFormatting.DARK_RED +import net.minecraft.util.EnumChatFormatting.DARK_PURPLE +import net.minecraft.util.EnumChatFormatting.GOLD +import net.minecraft.util.EnumChatFormatting.GRAY +import net.minecraft.util.EnumChatFormatting.DARK_GRAY +import net.minecraft.util.EnumChatFormatting.BLUE +import net.minecraft.util.EnumChatFormatting.GREEN +import net.minecraft.util.EnumChatFormatting.AQUA +import net.minecraft.util.EnumChatFormatting.RED +import net.minecraft.util.EnumChatFormatting.LIGHT_PURPLE +import net.minecraft.util.EnumChatFormatting.YELLOW +import net.minecraft.util.EnumChatFormatting.WHITE +import net.minecraft.util.EnumChatFormatting.OBFUSCATED +import net.minecraft.util.EnumChatFormatting.BOLD +import net.minecraft.util.EnumChatFormatting.STRIKETHROUGH +import net.minecraft.util.EnumChatFormatting.UNDERLINE +import net.minecraft.util.EnumChatFormatting.ITALIC +import net.minecraft.util.EnumChatFormatting.RESET +import net.minecraft.util.IChatComponent + +class SimpleChatMsg(msg: String) { + private var component: IChatComponent + + init { + component = ChatComponentText(msg) + } + + + fun default(): IChatComponent { + component.chatStyle = ChatStyle().setColor(RESET) + return component + } + + fun black(): IChatComponent { + component.chatStyle = ChatStyle().setColor(BLACK) + return component + } + + fun dark_blue(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_BLUE) + return component + } + + fun dark_green(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_GREEN) + return component + } + + fun dark_aqua(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_AQUA) + return component + } + + fun dark_red(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_RED) + return component + } + + fun dark_purple(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_PURPLE) + return component + } + + fun gold(): IChatComponent { + component.chatStyle = ChatStyle().setColor(GOLD) + return component + } + + fun gray(): IChatComponent { + component.chatStyle = ChatStyle().setColor(GRAY) + return component + } + + fun dark_gray(): IChatComponent { + component.chatStyle = ChatStyle().setColor(DARK_GRAY) + return component + } + + fun blue(): IChatComponent { + component.chatStyle = ChatStyle().setColor(BLUE) + return component + } + + fun green(): IChatComponent { + component.chatStyle = ChatStyle().setColor(GREEN) + return component + } + + fun aqua(): IChatComponent { + component.chatStyle = ChatStyle().setColor(AQUA) + return component + } + + fun red(): IChatComponent { + component.chatStyle = ChatStyle().setColor(RED) + return component + } + + fun light_purple(): IChatComponent { + component.chatStyle = ChatStyle().setColor(LIGHT_PURPLE) + return component + } + + fun yellow(): IChatComponent { + component.chatStyle = ChatStyle().setColor(YELLOW) + return component + } + + fun white(): IChatComponent { + component.chatStyle = ChatStyle().setColor(WHITE) + return component + } + + fun obfuscated(): IChatComponent { + component.chatStyle = ChatStyle().setColor(OBFUSCATED) + return component + } + + fun bold(): IChatComponent { + component.chatStyle = ChatStyle().setColor(BOLD) + return component + } + + fun strikethrough(): IChatComponent { + component.chatStyle = ChatStyle().setColor(STRIKETHROUGH) + return component + } + + fun underline(): IChatComponent { + component.chatStyle = ChatStyle().setColor(UNDERLINE) + return component + } + + fun italic(): IChatComponent { + component.chatStyle = ChatStyle().setColor(ITALIC) + return component + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/itzilly/sbt/util/WorldUtils.kt b/src/main/kotlin/com/github/itzilly/sbt/util/WorldUtils.kt new file mode 100644 index 0000000..6598736 --- /dev/null +++ b/src/main/kotlin/com/github/itzilly/sbt/util/WorldUtils.kt @@ -0,0 +1,19 @@ +package com.github.itzilly.sbt.util + +import net.minecraft.client.Minecraft + +fun getMiniServerIdFromScoreboard(): String? { + val scoreboard = Minecraft.getMinecraft().theWorld?.scoreboard ?: return null + val objective = scoreboard.getObjectiveInDisplaySlot(1) ?: return null + + return scoreboard.getSortedScores(objective) + .mapNotNull { score -> + try { + score.playerName + } catch (e: Exception) { + null + } + } + .map { it.replace(Regex("§."), "").trim() } + .firstOrNull { it.matches(Regex("^\\[mini\\w+]$")) } +} \ No newline at end of file diff --git a/src/main/resources/assets/sbt/lang/en_US.lang b/src/main/resources/assets/sbt/lang/en_US.lang index 4bb98c7..d9b77f7 100644 --- a/src/main/resources/assets/sbt/lang/en_US.lang +++ b/src/main/resources/assets/sbt/lang/en_US.lang @@ -1,4 +1,7 @@ -key.categories.sbt=SkyBlock Tweaks -key.addWaypoint=Add Route Point -key.finishWaypoints=Finish Route -key.clearWaypoints=Clear Route Points \ No newline at end of file +key.searchGrotto=Search Grotto +key.clearGrotto=Clear Grotto +key.addRouteNode=Add Route Node +key.removeLastNode=Remove Last Node +key.finishRoute=Finish Route + +key.categories.sbt=SkyBlock Tweaks \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index ab6d5dd..14ae447 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -1,14 +1,14 @@ [ { "modid": "${modid}", - "name": "Xample Mod", - "description": "A mod that is used as an example.", + "name": "SkyBlock Tweaks", + "description": "Simple tweaks for Hypixel Skyblock", "version": "${version}", "mcversion": "${mcversion}", - "url": "https://github.com/romangraef/Forge1.8.9Template/", + "url": "http://itzilly.com/illyum/SkyBlockTweaks", "updateUrl": "", "authorList": [ - "You" + "itzilly" ], "credits": "", "logoFile": "",