diff --git a/.editorconfig b/.editorconfig index 17418e66..c3aca48c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,12 +12,12 @@ indent_size = 2 [*.kt] max_line_length = 120 -ij_kotlin_imports_layout = *, java, javax, kotlin, static * +ij_kotlin_imports_layout = * ij_kotlin_allow_trailing_comma = true [*.java] max_line_length = 120 -ij_java_imports_layout = *, java, javax, static * +ij_java_imports_layout = * [*.{kts,xml,yml,json,md}] indent_style = space diff --git a/build.gradle.kts b/build.gradle.kts index b935bc71..3e69764e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ base { detekt { buildUponDefaultConfig = true config.setFrom(rootProject.file("config/detekt/detekt.yml")) - failOnSeverity = FailOnSeverity.Never + failOnSeverity = FailOnSeverity.Warning allRules = false ignoredBuildTypes = listOf() } @@ -55,7 +55,7 @@ dependencies { jij(libs.skija.shared) jij(libs.discordIpc) - runtimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.2") + runtimeOnly(libs.devAuth) } addResolvedDependencies(jij, "compileOnly", "include", "api") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5639e0e4..d68e9604 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ fabric-kotlin = "1.13.12+kotlin.2.4.0" skija-version = "0.143.11" discordIpc = "0.6.0-SNAPSHOT" +devAuth = "1.2.2" # Plugins Versions kotlin = "2.4.0" @@ -23,6 +24,7 @@ fabric-kotlin = { group = "net.fabricmc", name = "fabric-language-kotlin", versi skija-shared = { group = "io.github.humbleui", name = "skija-shared", version.ref = "skija-version" } discordIpc = { group = "com.jagrosh", name = "DiscordIPC", version.ref = "discordIpc" } +devAuth = { group = "me.djtheredstoner", name = "DevAuth-fabric", version.ref = "devAuth" } [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/src/main/kotlin/org/cobalt/Cobalt.kt b/src/main/kotlin/org/cobalt/Cobalt.kt index a7cb1ad7..fb9ade0b 100644 --- a/src/main/kotlin/org/cobalt/Cobalt.kt +++ b/src/main/kotlin/org/cobalt/Cobalt.kt @@ -12,7 +12,6 @@ import org.cobalt.command.CommandManager import org.cobalt.module.ModuleManager import org.cobalt.ui.theme.ThemeManager import org.cobalt.util.failsafe.FailsafeManager -import org.cobalt.util.failsafe.FailsafeUtils import org.cobalt.util.input.KeyMappingHandler import org.cobalt.util.render.skia.SkiaPIP import org.cobalt.util.scheduling.Multithreading @@ -58,7 +57,6 @@ object Cobalt : ClientModInitializer { CommandManager.registerCommands() FailsafeManager.initialize() - FailsafeUtils.init() KeyMappingHandler.registerKeyMappings() Multithreading.runAsync { diff --git a/src/main/kotlin/org/cobalt/command/annotation/SubCommand.kt b/src/main/kotlin/org/cobalt/command/annotation/SubCommand.kt index 9339d0d9..76d74315 100644 --- a/src/main/kotlin/org/cobalt/command/annotation/SubCommand.kt +++ b/src/main/kotlin/org/cobalt/command/annotation/SubCommand.kt @@ -2,4 +2,4 @@ package org.cobalt.command.annotation @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) -annotation class SubCommand(val name: String = "") +annotation class SubCommand diff --git a/src/main/kotlin/org/cobalt/command/impl/MainCommand.kt b/src/main/kotlin/org/cobalt/command/impl/MainCommand.kt index 578efdd4..aec3a0ce 100644 --- a/src/main/kotlin/org/cobalt/command/impl/MainCommand.kt +++ b/src/main/kotlin/org/cobalt/command/impl/MainCommand.kt @@ -1,7 +1,5 @@ package org.cobalt.command.impl -import java.io.File -import net.minecraft.world.entity.Entity import net.minecraft.world.entity.player.Player import net.minecraft.world.phys.EntityHitResult import net.minecraft.world.phys.HitResult @@ -12,11 +10,8 @@ import org.cobalt.command.annotation.SubCommand import org.cobalt.ui.screen.ConfigScreen import org.cobalt.ui.screen.HudEditorScreen import org.cobalt.event.EventBus -import org.cobalt.module.impl.failsafes.TeleportFailsafe import org.cobalt.module.impl.misc.Rotations -import org.cobalt.util.audio.AudioManager import org.cobalt.util.chat.ChatUtils -import org.cobalt.util.failsafe.FailsafeManager import org.cobalt.util.rotation.data.Rotation import org.cobalt.util.scheduling.TickScheduler import org.cobalt.util.server.Scoreboard diff --git a/src/main/kotlin/org/cobalt/module/ModuleManager.kt b/src/main/kotlin/org/cobalt/module/ModuleManager.kt index b2e460e5..d0bb9a9c 100644 --- a/src/main/kotlin/org/cobalt/module/ModuleManager.kt +++ b/src/main/kotlin/org/cobalt/module/ModuleManager.kt @@ -1,5 +1,6 @@ package org.cobalt.module +import java.util.concurrent.CopyOnWriteArraySet import net.minecraft.client.gui.screens.LevelLoadingScreen import net.minecraft.client.gui.screens.ProgressScreen import org.cobalt.Cobalt.minecraft @@ -27,7 +28,7 @@ import org.cobalt.util.render.skia.SkiaPIP object ModuleManager { - val modules = mutableSetOf() + val modules = CopyOnWriteArraySet() var lastScript: Script? = null var currentScript: Script? = null diff --git a/src/main/kotlin/org/cobalt/module/impl/combat/AutoClicker.kt b/src/main/kotlin/org/cobalt/module/impl/combat/AutoClicker.kt index eb56c17a..a7c496d7 100644 --- a/src/main/kotlin/org/cobalt/module/impl/combat/AutoClicker.kt +++ b/src/main/kotlin/org/cobalt/module/impl/combat/AutoClicker.kt @@ -56,6 +56,9 @@ object AutoClicker : Module( options = arrayOf("All", "Entity Only", "No Blocks"), ) + private val attackModeType: AttackMode + get() = AttackMode.fromOptionIndex(attackMode) + private val mobFilter by TextSetting( name = "Mob Filter", description = "Only attack listed names (comma-separated)", @@ -104,11 +107,10 @@ object AutoClicker : Module( val player = minecraft.player ?: return false val hit = minecraft.hitResult - val attackAllowed = when (attackMode) { - 0 -> true - 1 -> hit?.type == HitResult.Type.ENTITY - 2 -> hit?.type != HitResult.Type.BLOCK - else -> false + val attackAllowed = when (attackModeType) { + AttackMode.ALL -> true + AttackMode.ENTITY_ONLY -> hit?.type == HitResult.Type.ENTITY + AttackMode.NO_BLOCKS -> hit?.type != HitResult.Type.BLOCK } if (!attackAllowed || (waitForReload && player.getAttackStrengthScale(0f) < 1.0f)) { @@ -134,4 +136,14 @@ object AutoClicker : Module( return (base + jitter).coerceAtLeast(1.0).toLong() } + private enum class AttackMode(val optionIndex: Int) { + ALL(0), + ENTITY_ONLY(1), + NO_BLOCKS(2); + + companion object { + fun fromOptionIndex(index: Int): AttackMode = entries.firstOrNull { it.optionIndex == index } ?: ALL + } + } + } diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/ChatMentionFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/ChatMentionFailsafe.kt index a5305672..ab9d9c67 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/ChatMentionFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/ChatMentionFailsafe.kt @@ -1,15 +1,12 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import net.minecraft.network.chat.Component -import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket import net.minecraft.network.protocol.game.ClientboundSystemChatPacket import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.PacketEvent import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.ui.component.setting.impl.CheckboxSetting -import org.cobalt.ui.component.setting.impl.ModeSetting import org.cobalt.ui.component.setting.impl.TextSetting import org.cobalt.util.chat.ChatUtils import org.cobalt.util.chat.MessageType @@ -32,7 +29,7 @@ object ChatMentionFailsafe : Failsafe("Chat Mention", 10, false) { @SubscribeEvent fun onChatMessage(event: PacketEvent.Receive) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return val packet = event.packet as? ClientboundSystemChatPacket ?: return val message = packet.content.string val sender = getSenderFromComponent(packet.content) diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/PlayerCheckFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/PlayerCheckFailsafe.kt index ca9851af..3f6fac94 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/PlayerCheckFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/PlayerCheckFailsafe.kt @@ -1,13 +1,9 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import net.minecraft.core.BlockPos -import net.minecraft.world.phys.EntityHitResult -import net.minecraft.world.phys.HitResult import org.cobalt.Cobalt import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.TickEvent -import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.util.client.PlayerUtils import org.cobalt.util.failsafe.FailsafeManager @@ -17,7 +13,7 @@ object PlayerCheckFailsafe : Failsafe("Player Check", 10, false) { @SubscribeEvent fun onTick(ignored: TickEvent.End) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return val player = PlayerUtils.player ?: return val level = Cobalt.minecraft.level ?: return diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/RotationFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/RotationFailsafe.kt index e138924e..2ec70207 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/RotationFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/RotationFailsafe.kt @@ -1,15 +1,13 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import org.cobalt.Cobalt -import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.util.failsafe.FailsafeManager import org.cobalt.util.rotation.data.Rotation object RotationFailsafe : Failsafe("Rotation", 10, false) { fun onRotation(currentRot: Rotation, newRot: Rotation) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return val player = Cobalt.minecraft.player ?: return if (currentRot == newRot) return // I think this is needed? not sure diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/SlotChangeFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/SlotChangeFailsafe.kt index 5cd28c97..9ace42de 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/SlotChangeFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/SlotChangeFailsafe.kt @@ -1,19 +1,16 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket import org.cobalt.Cobalt import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.PacketEvent -import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.util.failsafe.FailsafeManager -import org.cobalt.util.inventory.InventoryUtils object SlotChangeFailsafe : Failsafe("Slot Change", 10, false) { @SubscribeEvent fun onServerItemChange(event: PacketEvent.Receive) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return val player = Cobalt.minecraft.player ?: return val packet = event.packet as? ClientboundSetHeldSlotPacket ?: return diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/TeleportFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/TeleportFailsafe.kt index e1619768..7427b18e 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/TeleportFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/TeleportFailsafe.kt @@ -1,6 +1,5 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import net.minecraft.core.BlockPos import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket import net.minecraft.network.protocol.game.ServerboundChatCommandPacket @@ -8,20 +7,18 @@ import net.minecraft.network.protocol.game.ServerboundUseItemPacket import org.cobalt.Cobalt.minecraft import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.PacketEvent -import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.util.chat.ChatUtils import org.cobalt.util.chat.MessageType import org.cobalt.util.client.PlayerUtils.player import org.cobalt.util.failsafe.FailsafeManager import org.cobalt.util.inventory.InventoryUtils -import org.cobalt.util.inventory.ItemUtils import org.cobalt.util.rotation.data.Rotation object TeleportFailsafe : Failsafe("Teleport", 10, false) { @SubscribeEvent fun onTeleport(event: PacketEvent.Any) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return if (minecraft.level == null) return when (val packet = event.packet) { is ClientboundPlayerPositionPacket -> { diff --git a/src/main/kotlin/org/cobalt/module/impl/failsafes/VelocityFailsafe.kt b/src/main/kotlin/org/cobalt/module/impl/failsafes/VelocityFailsafe.kt index 77c50196..8dae5d5a 100644 --- a/src/main/kotlin/org/cobalt/module/impl/failsafes/VelocityFailsafe.kt +++ b/src/main/kotlin/org/cobalt/module/impl/failsafes/VelocityFailsafe.kt @@ -1,13 +1,11 @@ package org.cobalt.module.impl.failsafes -import net.fabricmc.loader.api.FabricLoader import net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket import net.minecraft.world.level.block.Blocks import net.minecraft.world.phys.Vec3 import org.cobalt.Cobalt import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.PacketEvent -import org.cobalt.module.ModuleManager import org.cobalt.module.type.Failsafe import org.cobalt.util.client.PlayerUtils import org.cobalt.util.failsafe.FailsafeManager @@ -27,7 +25,7 @@ object VelocityFailsafe : Failsafe("Velocity", 10, false) { @SubscribeEvent fun onVelo(event: PacketEvent.Receive) { - if (!ModuleManager.isScriptRunning() && !FabricLoader.getInstance().isDevelopmentEnvironment) return + if (!shouldReactToEvents()) return val player = Cobalt.minecraft.player ?: return val packet = event.packet as? ClientboundSetEntityMotionPacket ?: return diff --git a/src/main/kotlin/org/cobalt/module/impl/visual/PlayerESP.kt b/src/main/kotlin/org/cobalt/module/impl/visual/PlayerESP.kt index f93963b4..05e693e9 100644 --- a/src/main/kotlin/org/cobalt/module/impl/visual/PlayerESP.kt +++ b/src/main/kotlin/org/cobalt/module/impl/visual/PlayerESP.kt @@ -21,8 +21,22 @@ object PlayerESP : Module(name = "PlayerESP", category = ModuleCategory.VISUAL) options = arrayOf("Box", "Target Beam", "Outline", "Beam & Outline") ) + private enum class EspType(val optionIndex: Int) { + BOX(0), + TARGET_BEAM(1), + OUTLINE(2), + BEAM_AND_OUTLINE(3); + + companion object { + fun fromOptionIndex(index: Int): EspType = entries.firstOrNull { it.optionIndex == index } ?: BOX + } + } + + private val espTypeMode: EspType + get() = EspType.fromOptionIndex(espType) + fun shouldOutline(entity: Entity): Boolean { - if (espType != 2 && espType != 3) { + if (espTypeMode != EspType.OUTLINE && espTypeMode != EspType.BEAM_AND_OUTLINE) { return false } @@ -45,7 +59,7 @@ object PlayerESP : Module(name = "PlayerESP", category = ModuleCategory.VISUAL) @SubscribeEvent fun onWorldRender(ignored: WorldEvent.BeforeGizmos) { - if (espType == 2) { + if (espTypeMode == EspType.OUTLINE) { return } @@ -56,24 +70,20 @@ object PlayerESP : Module(name = "PlayerESP", category = ModuleCategory.VISUAL) if (player.uuid.version() == 2) return@forEach - when (espType) { - 0 -> GizmoRenderer.drawEntityOutline( + when (espTypeMode) { + EspType.BOX -> GizmoRenderer.drawEntityOutline( entity = player, color = ThemeManager.activeTheme.accentPrimary, esp = true ) - 1 -> GizmoRenderer.drawTargetBeam( + EspType.TARGET_BEAM, EspType.BEAM_AND_OUTLINE -> GizmoRenderer.drawTargetBeam( entity = player, color = ThemeManager.activeTheme.accentPrimary, esp = true ) - 3 -> GizmoRenderer.drawTargetBeam( // both beam and esp - entity = player, - color = ThemeManager.activeTheme.accentPrimary, - esp = true - ) + EspType.OUTLINE -> Unit } } } diff --git a/src/main/kotlin/org/cobalt/module/type/Failsafe.kt b/src/main/kotlin/org/cobalt/module/type/Failsafe.kt index 98721a0e..5b49c867 100644 --- a/src/main/kotlin/org/cobalt/module/type/Failsafe.kt +++ b/src/main/kotlin/org/cobalt/module/type/Failsafe.kt @@ -1,7 +1,9 @@ package org.cobalt.module.type +import net.fabricmc.loader.api.FabricLoader import org.cobalt.module.Module import org.cobalt.module.ModuleCategory +import org.cobalt.module.ModuleManager abstract class Failsafe @JvmOverloads constructor( name: String, @@ -13,6 +15,9 @@ abstract class Failsafe @JvmOverloads constructor( startValue = startValue ) { + protected fun shouldReactToEvents(): Boolean = + ModuleManager.isScriptRunning() || FabricLoader.getInstance().isDevelopmentEnvironment + abstract fun resetStates() abstract fun performReaction(): ReactionResult? diff --git a/src/main/kotlin/org/cobalt/ui/UIComponent.kt b/src/main/kotlin/org/cobalt/ui/UIComponent.kt index 05b831e0..4423c13b 100644 --- a/src/main/kotlin/org/cobalt/ui/UIComponent.kt +++ b/src/main/kotlin/org/cobalt/ui/UIComponent.kt @@ -5,6 +5,8 @@ import net.minecraft.client.input.KeyEvent import org.cobalt.ui.theme.Theme import org.cobalt.ui.theme.ThemeManager +const val PADDING = 20f + abstract class UIComponent( var xPos: Float = 0f, var yPos: Float = 0f, diff --git a/src/main/kotlin/org/cobalt/ui/animation/HoverFade.kt b/src/main/kotlin/org/cobalt/ui/animation/HoverFade.kt new file mode 100644 index 00000000..9999cbd6 --- /dev/null +++ b/src/main/kotlin/org/cobalt/ui/animation/HoverFade.kt @@ -0,0 +1,25 @@ +package org.cobalt.ui.animation + +import java.awt.Color + +class HoverFade(duration: Long = 150L) { + + private val colorAnimation = ColorAnimation(duration) + private val alphaAnimation = EaseOutAnimation(duration) + private var wasHovering = false + + fun update(hovering: Boolean) { + if (hovering != wasHovering) { + colorAnimation.start() + alphaAnimation.start() + wasHovering = hovering + } + } + + fun overlayAlpha(hovering: Boolean, from: Float = 0f, to: Float = 40f): Int = + alphaAnimation.get(from, to, !hovering).toInt() + + fun color(from: Color, to: Color, hovering: Boolean): Color = + colorAnimation.get(from, to, !hovering) + +} diff --git a/src/main/kotlin/org/cobalt/ui/component/ModuleComponent.kt b/src/main/kotlin/org/cobalt/ui/component/ModuleComponent.kt index 6a2f1c81..b98848df 100644 --- a/src/main/kotlin/org/cobalt/ui/component/ModuleComponent.kt +++ b/src/main/kotlin/org/cobalt/ui/component/ModuleComponent.kt @@ -1,6 +1,7 @@ package org.cobalt.ui.component import org.cobalt.module.Module +import org.cobalt.ui.PADDING import org.cobalt.ui.UIComponent import org.cobalt.ui.animation.EaseOutAnimation import org.cobalt.util.render.SkiaRenderer @@ -126,7 +127,6 @@ class ModuleComponent( private const val BASE_HEIGHT = 60f private const val FONT_SIZE = 16f - private const val PADDING = 20f } } diff --git a/src/main/kotlin/org/cobalt/ui/component/ScriptComponent.kt b/src/main/kotlin/org/cobalt/ui/component/ScriptComponent.kt index d057dcfd..3fa5f7c9 100644 --- a/src/main/kotlin/org/cobalt/ui/component/ScriptComponent.kt +++ b/src/main/kotlin/org/cobalt/ui/component/ScriptComponent.kt @@ -2,6 +2,7 @@ package org.cobalt.ui.component import org.cobalt.module.ModuleManager import org.cobalt.module.type.Script +import org.cobalt.ui.PADDING import org.cobalt.ui.UIComponent import org.cobalt.ui.animation.ColorAnimation import org.cobalt.ui.animation.EaseOutAnimation @@ -123,7 +124,6 @@ class ScriptComponent(val script: Script) : UIComponent( val WIDTH = (TopbarComponent.width - 60) / 2f val pauseIcon = SkiaRenderer.createImage("/assets/cobalt/ui/pause.svg") - private const val PADDING = 20f private const val FONT_SIZE = 20f private const val ICON_SIZE = 30f } diff --git a/src/main/kotlin/org/cobalt/ui/component/TopbarComponent.kt b/src/main/kotlin/org/cobalt/ui/component/TopbarComponent.kt index 5331b12b..4b7e3ab0 100644 --- a/src/main/kotlin/org/cobalt/ui/component/TopbarComponent.kt +++ b/src/main/kotlin/org/cobalt/ui/component/TopbarComponent.kt @@ -1,6 +1,7 @@ package org.cobalt.ui.component import org.cobalt.Cobalt.minecraft +import org.cobalt.ui.PADDING import org.cobalt.ui.UIComponent import org.cobalt.ui.component.button.IconButton import org.cobalt.ui.page.impl.ScriptsPage @@ -61,7 +62,7 @@ object TopbarComponent : UIComponent() { corners = arrayOf(SkiaCorner.TOP_RIGHT) ) - val textX = xPos + INNER_PADDING + 10f + val textX = xPos + PADDING + 10f val textY = yPos + (height - CURRENT_PAGE_TITLE_FONT) / 2 SkiaRenderer.text( @@ -73,7 +74,7 @@ object TopbarComponent : UIComponent() { color = theme.textPrimary ) - val searchBarX = xPos + width - SEARCHBAR_WIDTH - INNER_PADDING + val searchBarX = xPos + width - SEARCHBAR_WIDTH - PADDING val searchBarY = yPos + (height - SEARCHBAR_HEIGHT) / 2 searchBar @@ -97,7 +98,6 @@ object TopbarComponent : UIComponent() { private const val SEARCHBAR_WIDTH = 250f private const val SEARCHBAR_HEIGHT = 40f private const val CURRENT_PAGE_TITLE_FONT = 19f - private const val INNER_PADDING = 20f private const val BUTTON_SPACING = 10f } diff --git a/src/main/kotlin/org/cobalt/ui/component/button/IconButton.kt b/src/main/kotlin/org/cobalt/ui/component/button/IconButton.kt index 4724be58..c1e5d9ab 100644 --- a/src/main/kotlin/org/cobalt/ui/component/button/IconButton.kt +++ b/src/main/kotlin/org/cobalt/ui/component/button/IconButton.kt @@ -1,8 +1,7 @@ package org.cobalt.ui.component.button import org.cobalt.ui.UIComponent -import org.cobalt.ui.animation.ColorAnimation -import org.cobalt.ui.animation.EaseOutAnimation +import org.cobalt.ui.animation.HoverFade import org.cobalt.util.color.updateAlpha import org.cobalt.util.input.Mouse import org.cobalt.util.render.SkiaRenderer @@ -16,23 +15,15 @@ class IconButton( ) { private val icon = SkiaRenderer.createImage(resourcePath) - private val colorAnimation = ColorAnimation(150L) - private val alphaAnimation = EaseOutAnimation(150L) - - private var wasHovering = false + private val hoverFade = HoverFade() override fun renderComponent() { val hovering = Mouse.isHoveringOver(xPos, yPos, width, height) + hoverFade.update(hovering) - if (hovering != wasHovering) { - colorAnimation.start() - alphaAnimation.start() - wasHovering = hovering - } - - val alpha = alphaAnimation.get(0f, 40f, !hovering).toInt() - val borderColor = colorAnimation.get(theme.border, theme.accentPrimary, !hovering) - val iconColor = colorAnimation.get(theme.textMuted, theme.accentPrimary, !hovering) + val alpha = hoverFade.overlayAlpha(hovering) + val borderColor = hoverFade.color(theme.border, theme.accentPrimary, hovering) + val iconColor = hoverFade.color(theme.textMuted, theme.accentPrimary, hovering) SkiaRenderer.roundedRect( x = xPos, diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/Setting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/Setting.kt index 91649389..0ef7bd35 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/Setting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/Setting.kt @@ -4,6 +4,7 @@ import com.google.gson.JsonElement import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty +import org.cobalt.ui.PADDING import org.cobalt.ui.UIComponent import org.cobalt.ui.component.ModuleComponent import org.cobalt.ui.component.setting.impl.InfoSetting @@ -76,7 +77,6 @@ abstract class Setting( companion object { protected const val BASE_HEIGHT = 60f - protected const val PADDING = 20f protected const val NAME_SIZE = 14f protected const val DESCRIPTION_SIZE = 12f protected const val TEXT_SPACING_Y = 5f diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/ButtonSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/ButtonSetting.kt index 7b800244..21399bb4 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/ButtonSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/ButtonSetting.kt @@ -2,8 +2,8 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive -import org.cobalt.ui.animation.ColorAnimation -import org.cobalt.ui.animation.EaseOutAnimation +import org.cobalt.ui.PADDING +import org.cobalt.ui.animation.HoverFade import org.cobalt.ui.component.setting.Setting import org.cobalt.util.color.updateAlpha import org.cobalt.util.input.Mouse @@ -19,29 +19,21 @@ class ButtonSetting( override fun read(element: JsonElement) = Unit override fun write(): JsonElement = JsonPrimitive("") - private val colorAnim = ColorAnimation(150L) - private val alphaAnim = EaseOutAnimation(150L) - private var wasHovering = false + private val hoverFade = HoverFade() private val buttonWidth = SkiaRenderer.textWidth(SkiaRenderer.regularFont, buttonLabel, FONT_SIZE) + 30f override fun renderSetting() { - val buttonWidth = buttonWidth val startX = xPos + width - buttonWidth - PADDING val startY = yPos + (height - BUTTON_HEIGHT) / 2 val hovering = Mouse.isHoveringOver(startX, startY, buttonWidth, BUTTON_HEIGHT) + hoverFade.update(hovering) - if (hovering != wasHovering) { - colorAnim.start() - alphaAnim.start() - wasHovering = hovering - } - - val alpha = alphaAnim.get(0f, 40f, !hovering).toInt() - val borderColor = colorAnim.get(theme.border, theme.accentPrimary, !hovering) - val textColor = colorAnim.get(theme.textPrimary, theme.accentPrimary, !hovering) + val alpha = hoverFade.overlayAlpha(hovering) + val borderColor = hoverFade.color(theme.border, theme.accentPrimary, hovering) + val textColor = hoverFade.color(theme.textPrimary, theme.accentPrimary, hovering) SkiaRenderer.roundedRect( x = startX, diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/CheckboxSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/CheckboxSetting.kt index d70d6c6e..cf42c42a 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/CheckboxSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/CheckboxSetting.kt @@ -2,6 +2,7 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.animation.ColorAnimation import org.cobalt.ui.animation.EaseOutAnimation import org.cobalt.ui.component.setting.Setting diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/InfoSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/InfoSetting.kt index f7c25022..808c4c21 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/InfoSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/InfoSetting.kt @@ -2,6 +2,7 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.component.setting.Setting import org.cobalt.util.color.updateAlpha import org.cobalt.util.render.SkiaRenderer diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/KeyBindSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/KeyBindSetting.kt index 77312339..15607391 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/KeyBindSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/KeyBindSetting.kt @@ -4,6 +4,7 @@ import com.google.gson.JsonElement import com.google.gson.JsonPrimitive import com.mojang.blaze3d.platform.InputConstants import net.minecraft.client.input.KeyEvent +import org.cobalt.ui.PADDING import org.cobalt.ui.component.setting.Setting import org.cobalt.util.input.Mouse import org.cobalt.util.render.SkiaRenderer diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/ModeSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/ModeSetting.kt index 2d7c74b4..ed0d0743 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/ModeSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/ModeSetting.kt @@ -2,6 +2,7 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.component.setting.Setting import org.cobalt.util.input.Mouse import org.cobalt.util.render.SkiaRenderer @@ -66,10 +67,6 @@ class ModeSetting( } override fun mouseClicked(button: Int): Boolean { - if (options.isEmpty()) { - return false - } - val display = options.getOrNull(value).orEmpty() val buttonWidth = SkiaRenderer.textWidth(SkiaRenderer.regularFont, display, FONT_SIZE) + 30f val startX = xPos + width - buttonWidth - PADDING diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/RangeSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/RangeSetting.kt index 9b82b1c6..1c218f49 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/RangeSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/RangeSetting.kt @@ -3,9 +3,9 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.component.setting.Setting import org.cobalt.util.input.Mouse -import org.cobalt.util.render.SkiaRenderer class RangeSetting( name: String, @@ -35,72 +35,36 @@ class RangeSetting( private var rawEnd: Float = defaultValue.second.toFloat() override val height: Float - get() = BASE_HEIGHT + TRACK_ROW_HEIGHT + get() = BASE_HEIGHT + TrackSettingHelper.TRACK_ROW_HEIGHT override fun renderSetting() { val text = "${value.first} – ${value.second}" - val boxWidth = boxWidth(text) + val boxWidth = TrackSettingHelper.valueBoxWidth(text) val boxX = xPos + width - PADDING - boxWidth - val boxY = yPos + (BASE_HEIGHT - VALUE_BOX_HEIGHT) / 2 + val boxY = yPos + (BASE_HEIGHT - TrackSettingHelper.VALUE_BOX_HEIGHT) / 2 - SkiaRenderer.roundedRect( + TrackSettingHelper.drawValueBox( x = boxX, y = boxY, width = boxWidth, - height = VALUE_BOX_HEIGHT, - radius = 5f, - color = theme.backgroundPrimary - ) - - SkiaRenderer.roundedOutline( - x = boxX, - y = boxY, - width = boxWidth, - height = VALUE_BOX_HEIGHT, - thickness = 1f, - radius = 5f, - color = theme.border - ) - - val textWidth = SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) - - SkiaRenderer.text( - font = SkiaRenderer.regularFont, text = text, - x = boxX + (boxWidth - textWidth) / 2, - y = boxY + (VALUE_BOX_HEIGHT - FONT_SIZE) / 2, - size = FONT_SIZE, - color = theme.textPrimary + background = theme.backgroundPrimary, + border = theme.border, + textColor = theme.textPrimary ) val geometry = trackGeometry() - SkiaRenderer.roundedRect( - x = geometry.startX, - y = geometry.trackY - 2f, - width = geometry.trackWidth, - height = 4f, - radius = 3f, - color = theme.backgroundPrimary - ) - - SkiaRenderer.roundedRect( - x = geometry.startKnobX, - y = geometry.trackY - 2f, - width = (geometry.endKnobX - geometry.startKnobX).coerceAtLeast(0f), - height = 4f, - radius = 3f, - color = theme.accentPrimary - ) - - SkiaRenderer.circle( - geometry.startKnobX, geometry.trackY, - KNOB_RADIUS, theme.textPrimary - ) - - SkiaRenderer.circle( - geometry.endKnobX, geometry.trackY, - KNOB_RADIUS, theme.textPrimary + TrackSettingHelper.drawTrack( + startX = geometry.startX, + trackWidth = geometry.trackWidth, + trackY = geometry.trackY, + fillStartX = geometry.startKnobX, + fillEndX = geometry.endKnobX, + knobXs = listOf(geometry.startKnobX, geometry.endKnobX), + trackBackground = theme.backgroundPrimary, + fillColor = theme.accentPrimary, + knobColor = theme.textPrimary ) } @@ -111,19 +75,21 @@ class RangeSetting( val geometry = trackGeometry() + val knobRadius = TrackSettingHelper.KNOB_RADIUS + dragging = when { Mouse.isHoveringOver( - geometry.startKnobX - KNOB_RADIUS, - geometry.trackY - KNOB_RADIUS, - KNOB_RADIUS * 2, - KNOB_RADIUS * 2 + geometry.startKnobX - knobRadius, + geometry.trackY - knobRadius, + knobRadius * 2, + knobRadius * 2 ) -> Knob.START Mouse.isHoveringOver( - geometry.endKnobX - KNOB_RADIUS, - geometry.trackY - KNOB_RADIUS, - KNOB_RADIUS * 2, - KNOB_RADIUS * 2 + geometry.endKnobX - knobRadius, + geometry.trackY - knobRadius, + knobRadius * 2, + knobRadius * 2 ) -> Knob.END else -> return false @@ -197,7 +163,7 @@ class RangeSetting( private fun trackGeometry(): TrackGeometry { val startX = xPos + PADDING val trackWidth = width - PADDING * 2 - val trackY = yPos + BASE_HEIGHT + TRACK_MARGIN + val trackY = yPos + BASE_HEIGHT + TrackSettingHelper.TRACK_MARGIN val range = (max - min).toFloat().takeIf { it != 0f } ?: 1f val startKnobX = startX + (rawStart - min) / range * trackWidth val endKnobX = startX + (rawEnd - min) / range * trackWidth @@ -211,9 +177,6 @@ class RangeSetting( ) } - private fun boxWidth(text: String): Float = - SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) + VALUE_BOX_PADDING_X * 2f - private data class TrackGeometry( val startX: Float, val trackWidth: Float, @@ -226,13 +189,4 @@ class RangeSetting( START, END, NONE } - companion object { - private const val KNOB_RADIUS = 5f - private const val FONT_SIZE = 12f - private const val VALUE_BOX_HEIGHT = 30f - private const val VALUE_BOX_PADDING_X = 14f - private const val TRACK_ROW_HEIGHT = 15f - private const val TRACK_MARGIN = 5f - } - } diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/SliderSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/SliderSetting.kt index 49bb194c..73b71d2e 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/SliderSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/SliderSetting.kt @@ -2,9 +2,9 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.component.setting.Setting import org.cobalt.util.input.Mouse -import org.cobalt.util.render.SkiaRenderer class SliderSetting( name: String, @@ -15,7 +15,7 @@ class SliderSetting( ) : Setting(name, description, defaultValue) { override val height: Float - get() = BASE_HEIGHT + TRACK_ROW_HEIGHT + get() = BASE_HEIGHT + TrackSettingHelper.TRACK_ROW_HEIGHT override fun read(element: JsonElement) { value = element.asInt.coerceIn(min, max) @@ -29,63 +29,32 @@ class SliderSetting( override fun renderSetting() { val text = value.toString() - val boxWidth = valueBoxWidth(text) + val boxWidth = TrackSettingHelper.valueBoxWidth(text) val boxX = xPos + width - PADDING - boxWidth - val boxY = yPos + (BASE_HEIGHT - VALUE_BOX_HEIGHT) / 2 + val boxY = yPos + (BASE_HEIGHT - TrackSettingHelper.VALUE_BOX_HEIGHT) / 2 - SkiaRenderer.roundedRect( + TrackSettingHelper.drawValueBox( x = boxX, y = boxY, width = boxWidth, - height = VALUE_BOX_HEIGHT, - radius = 5f, - color = theme.backgroundPrimary - ) - - SkiaRenderer.roundedOutline( - x = boxX, - y = boxY, - width = boxWidth, - height = VALUE_BOX_HEIGHT, - thickness = 1f, - radius = 5f, - color = theme.border - ) - - val textWidth = SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) - - SkiaRenderer.text( - font = SkiaRenderer.regularFont, text = text, - x = boxX + (boxWidth - textWidth) / 2, - y = boxY + (VALUE_BOX_HEIGHT - FONT_SIZE) / 2, - size = FONT_SIZE, - color = theme.textPrimary + background = theme.backgroundPrimary, + border = theme.border, + textColor = theme.textPrimary ) val (startX, trackWidth, trackY, knobX) = trackGeometry() - SkiaRenderer.roundedRect( - x = startX, - y = trackY - 2f, - width = trackWidth, - height = 4f, - radius = 3f, - color = theme.backgroundPrimary - ) - - SkiaRenderer.roundedRect( - x = startX, - y = trackY - 2f, - width = (knobX - startX).coerceAtLeast(0f), - height = 4f, - radius = 3f, - color = theme.accentPrimary - ) - - SkiaRenderer.circle( - knobX, trackY, - KNOB_RADIUS, theme.textPrimary + TrackSettingHelper.drawTrack( + startX = startX, + trackWidth = trackWidth, + trackY = trackY, + fillStartX = startX, + fillEndX = knobX, + knobXs = listOf(knobX), + trackBackground = theme.backgroundPrimary, + fillColor = theme.accentPrimary, + knobColor = theme.textPrimary ) } @@ -95,11 +64,12 @@ class SliderSetting( } val (_, _, trackY, knobX) = trackGeometry() + val knobRadius = TrackSettingHelper.KNOB_RADIUS if ( !Mouse.isHoveringOver( - knobX - KNOB_RADIUS, trackY - KNOB_RADIUS, - KNOB_RADIUS * 2, KNOB_RADIUS * 2 + knobX - knobRadius, trackY - knobRadius, + knobRadius * 2, knobRadius * 2 ) ) { return false @@ -141,16 +111,13 @@ class SliderSetting( private fun trackGeometry(): TrackGeometry { val startX = xPos + PADDING val trackWidth = width - PADDING * 2 - val trackY = yPos + BASE_HEIGHT + TRACK_MARGIN + val trackY = yPos + BASE_HEIGHT + TrackSettingHelper.TRACK_MARGIN val range = (max - min).toFloat().takeIf { it != 0f } ?: 1f val displayValue = if (dragging) rawValue else value.toFloat() val knobX = startX + (displayValue - min) / range * trackWidth return TrackGeometry(startX, trackWidth, trackY, knobX) } - private fun valueBoxWidth(text: String): Float = - SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) + VALUE_BOX_PADDING_X * 2f - private data class TrackGeometry( val startX: Float, val trackWidth: Float, @@ -158,13 +125,4 @@ class SliderSetting( val knobX: Float, ) - companion object { - private const val KNOB_RADIUS = 5f - private const val FONT_SIZE = 12f - private const val VALUE_BOX_HEIGHT = 30f - private const val VALUE_BOX_PADDING_X = 14f - private const val TRACK_ROW_HEIGHT = 15f - private const val TRACK_MARGIN = 5f - } - } diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/TextSetting.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/TextSetting.kt index 3235bd84..60f04694 100644 --- a/src/main/kotlin/org/cobalt/ui/component/setting/impl/TextSetting.kt +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/TextSetting.kt @@ -2,6 +2,7 @@ package org.cobalt.ui.component.setting.impl import com.google.gson.JsonElement import com.google.gson.JsonPrimitive +import org.cobalt.ui.PADDING import org.cobalt.ui.component.TextInputComponent import org.cobalt.ui.component.setting.Setting diff --git a/src/main/kotlin/org/cobalt/ui/component/setting/impl/TrackSettingHelper.kt b/src/main/kotlin/org/cobalt/ui/component/setting/impl/TrackSettingHelper.kt new file mode 100644 index 00000000..62dab825 --- /dev/null +++ b/src/main/kotlin/org/cobalt/ui/component/setting/impl/TrackSettingHelper.kt @@ -0,0 +1,84 @@ +package org.cobalt.ui.component.setting.impl + +import java.awt.Color +import org.cobalt.util.render.SkiaRenderer + +internal object TrackSettingHelper { + + const val KNOB_RADIUS = 5f + const val FONT_SIZE = 12f + const val VALUE_BOX_HEIGHT = 30f + const val VALUE_BOX_PADDING_X = 14f + const val TRACK_ROW_HEIGHT = 15f + const val TRACK_MARGIN = 5f + + fun valueBoxWidth(text: String): Float = + SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) + VALUE_BOX_PADDING_X * 2f + + fun drawValueBox(x: Float, y: Float, width: Float, text: String, background: Color, border: Color, textColor: Color) { + SkiaRenderer.roundedRect( + x = x, + y = y, + width = width, + height = VALUE_BOX_HEIGHT, + radius = 5f, + color = background + ) + + SkiaRenderer.roundedOutline( + x = x, + y = y, + width = width, + height = VALUE_BOX_HEIGHT, + thickness = 1f, + radius = 5f, + color = border + ) + + val textWidth = SkiaRenderer.textWidth(SkiaRenderer.regularFont, text, FONT_SIZE) + + SkiaRenderer.text( + font = SkiaRenderer.regularFont, + text = text, + x = x + (width - textWidth) / 2, + y = y + (VALUE_BOX_HEIGHT - FONT_SIZE) / 2, + size = FONT_SIZE, + color = textColor + ) + } + + fun drawTrack( + startX: Float, + trackWidth: Float, + trackY: Float, + fillStartX: Float, + fillEndX: Float, + knobXs: List, + trackBackground: Color, + fillColor: Color, + knobColor: Color, + ) { + SkiaRenderer.roundedRect( + x = startX, + y = trackY - 2f, + width = trackWidth, + height = 4f, + radius = 3f, + color = trackBackground + ) + + SkiaRenderer.roundedRect( + x = fillStartX, + y = trackY - 2f, + width = (fillEndX - fillStartX).coerceAtLeast(0f), + height = 4f, + radius = 3f, + color = fillColor + ) + + knobXs.forEach { knobX -> + SkiaRenderer.circle(knobX, trackY, KNOB_RADIUS, knobColor) + } + } + +} diff --git a/src/main/kotlin/org/cobalt/ui/helper/MasonryColumnLayout.kt b/src/main/kotlin/org/cobalt/ui/helper/MasonryColumnLayout.kt new file mode 100644 index 00000000..ff330f39 --- /dev/null +++ b/src/main/kotlin/org/cobalt/ui/helper/MasonryColumnLayout.kt @@ -0,0 +1,21 @@ +package org.cobalt.ui.helper + +fun layoutMasonryColumns( + items: List, + columns: Int, + startY: Float, + gap: Float, + columnX: (col: Int) -> Float, + heightOf: (T) -> Float, + place: (item: T, x: Float, y: Float) -> Unit, +): Float { + val columnY = FloatArray(columns) { startY } + + items.forEachIndexed { index, item -> + val col = index % columns + place(item, columnX(col), columnY[col]) + columnY[col] += heightOf(item) + gap + } + + return columnY.max() +} diff --git a/src/main/kotlin/org/cobalt/ui/page/impl/ModulesPage.kt b/src/main/kotlin/org/cobalt/ui/page/impl/ModulesPage.kt index 5076c7c4..0b700904 100644 --- a/src/main/kotlin/org/cobalt/ui/page/impl/ModulesPage.kt +++ b/src/main/kotlin/org/cobalt/ui/page/impl/ModulesPage.kt @@ -3,9 +3,11 @@ package org.cobalt.ui.page.impl import org.cobalt.module.Module import org.cobalt.module.ModuleCategory import org.cobalt.module.ModuleManager +import org.cobalt.ui.PADDING import org.cobalt.ui.animation.EaseOutAnimation import org.cobalt.ui.component.ModuleComponent import org.cobalt.ui.helper.ScrollHelper +import org.cobalt.ui.helper.layoutMasonryColumns import org.cobalt.ui.page.Page import org.cobalt.ui.screen.ConfigScreen import org.cobalt.util.failsafe.FailsafeManager @@ -65,37 +67,23 @@ object ModulesPage : Page() { super.renderComponent() val pageOffset = openingOffset.get(-30f, 0f) - - val leftX = xPos + PADDING - var leftY = yPos + PADDING + pageOffset - scrollHelper.scrollOffset - val rightX = xPos + PADDING + ModuleComponent.WIDTH + COLUMN_GAP - var rightY = yPos + PADDING + pageOffset - scrollHelper.scrollOffset + val startY = yPos + PADDING + pageOffset - scrollHelper.scrollOffset SkiaRenderer.pushScissor(xPos, yPos, width, height) - moduleComponents.forEachIndexed { index, component -> - if (index % 2 == 0) { - component - .updateBounds(leftX, leftY) - .renderComponent() - - leftY += component.height + PADDING - } else { - component - .updateBounds(rightX, rightY) - .renderComponent() - - rightY += component.height + PADDING - } - } + val maxColumnY = layoutMasonryColumns( + items = moduleComponents, + columns = 2, + startY = startY, + gap = PADDING, + columnX = { col -> xPos + PADDING + col * (ModuleComponent.WIDTH + COLUMN_GAP) }, + heightOf = { it.height }, + place = { component, x, y -> component.updateBounds(x, y).renderComponent() } + ) SkiaRenderer.popScissor() - val contentHeight = maxOf( - leftY + scrollHelper.scrollOffset, - rightY + scrollHelper.scrollOffset - ) - yPos - + val contentHeight = maxColumnY + scrollHelper.scrollOffset - yPos scrollHelper.updateMaxScroll(contentHeight, height) } @@ -109,7 +97,6 @@ object ModulesPage : Page() { return false } - private const val PADDING = 20f private const val COLUMN_GAP = 20f } diff --git a/src/main/kotlin/org/cobalt/ui/page/impl/ScriptsPage.kt b/src/main/kotlin/org/cobalt/ui/page/impl/ScriptsPage.kt index b78d882a..3cd3aa4b 100644 --- a/src/main/kotlin/org/cobalt/ui/page/impl/ScriptsPage.kt +++ b/src/main/kotlin/org/cobalt/ui/page/impl/ScriptsPage.kt @@ -2,9 +2,11 @@ package org.cobalt.ui.page.impl import org.cobalt.module.ModuleManager import org.cobalt.module.type.Script +import org.cobalt.ui.PADDING import org.cobalt.ui.animation.EaseOutAnimation import org.cobalt.ui.component.ScriptComponent import org.cobalt.ui.helper.ScrollHelper +import org.cobalt.ui.helper.layoutMasonryColumns import org.cobalt.ui.page.Page import org.cobalt.util.input.Mouse import org.cobalt.util.render.SkiaRenderer @@ -75,23 +77,17 @@ object ScriptsPage : Page() { currentY += CATEGORY_FONT_SIZE + CATEGORY_MARGIN - val columnY = floatArrayOf(currentY, currentY) - val columnX = floatArrayOf( - xPos + PADDING, - xPos + PADDING + ScriptComponent.WIDTH + COLUMN_GAP + val maxColumnY = layoutMasonryColumns( + items = components, + columns = 2, + startY = currentY, + gap = PADDING, + columnX = { col -> xPos + PADDING + col * (ScriptComponent.WIDTH + COLUMN_GAP) }, + heightOf = { (component, _) -> component.height }, + place = { (component, _), x, y -> component.updateBounds(x, y).renderComponent() } ) - components.forEachIndexed { index, (component, _) -> - val col = index % 2 - - component - .updateBounds(columnX[col], columnY[col]) - .renderComponent() - - columnY[col] += component.height + PADDING - } - - currentY = columnY.max() + GROUP_GAP + currentY = maxColumnY + GROUP_GAP } SkiaRenderer.popScissor() @@ -109,7 +105,6 @@ object ScriptsPage : Page() { return false } - private const val PADDING = 20f private const val COLUMN_GAP = 20f private const val GROUP_GAP = 28f private const val CATEGORY_FONT_SIZE = 12f diff --git a/src/main/kotlin/org/cobalt/ui/page/impl/ThemesPage.kt b/src/main/kotlin/org/cobalt/ui/page/impl/ThemesPage.kt index c7b95233..f26f3d8f 100644 --- a/src/main/kotlin/org/cobalt/ui/page/impl/ThemesPage.kt +++ b/src/main/kotlin/org/cobalt/ui/page/impl/ThemesPage.kt @@ -1,6 +1,7 @@ package org.cobalt.ui.page.impl import kotlin.math.ceil +import org.cobalt.ui.PADDING import org.cobalt.ui.animation.EaseOutAnimation import org.cobalt.ui.component.ThemeComponent import org.cobalt.ui.component.button.IconButton @@ -95,7 +96,6 @@ object ThemesPage : Page() { return false } - private const val PADDING = 20f private const val COLUMNS = 3 private const val SPACING = 20f diff --git a/src/main/kotlin/org/cobalt/util/audio/AudioManager.kt b/src/main/kotlin/org/cobalt/util/audio/AudioManager.kt index b6736a66..41384b0b 100644 --- a/src/main/kotlin/org/cobalt/util/audio/AudioManager.kt +++ b/src/main/kotlin/org/cobalt/util/audio/AudioManager.kt @@ -1,25 +1,58 @@ package org.cobalt.util.audio import java.io.File +import java.util.concurrent.CopyOnWriteArrayList import javax.sound.sampled.AudioSystem import javax.sound.sampled.Clip import javax.sound.sampled.FloatControl +import javax.sound.sampled.Mixer import kotlin.math.log10 -import net.minecraft.client.Minecraft +import org.cobalt.Cobalt import org.slf4j.LoggerFactory object AudioManager { private val logger = LoggerFactory.getLogger(this::class.java) - private val clips = mutableListOf() - // YES I KNOW THIS PLAYS THRU SPEAKERS ISNTEAD OF HEADPHONES IDK WHY + private val clips = CopyOnWriteArrayList() + + private const val OPENAL_SOFT_PREFIX = "OpenAL Soft on " + + private fun resolveMixerInfo(): Mixer.Info? { + + val rawDeviceName = Cobalt.minecraft.options.soundDevice().get().takeIf { it.isNotBlank() } ?: return null + + val targetName = rawDeviceName.removePrefix(OPENAL_SOFT_PREFIX) + val mixers = AudioSystem.getMixerInfo() + + mixers.firstOrNull { it.name.equals(targetName, ignoreCase = true) }?.let { return it } + + val loose = mixers.firstOrNull { + it.name.contains(targetName, ignoreCase = true) || targetName.contains(it.name, ignoreCase = true) + } + if (loose == null) { + logger.warn("No javax.sound.sampled mixer matched Minecraft's sound device '{}'", rawDeviceName) + } + return loose + } + fun play(file: File, volume: Float = 1.0f) { if (!file.exists()) return try { val audioStream = AudioSystem.getAudioInputStream(file) - val clip = AudioSystem.getClip() + + val mixerInfo = resolveMixerInfo() + val clip: Clip = if (mixerInfo != null) { + try { + AudioSystem.getClip(mixerInfo) + } catch (e: Exception) { + logger.warn("Failed to open clip on Minecraft's sound device, falling back to default mixer", e) + AudioSystem.getClip() + } + } else { + AudioSystem.getClip() + } clip.open(audioStream) diff --git a/src/main/kotlin/org/cobalt/util/failsafe/FailsafeManager.kt b/src/main/kotlin/org/cobalt/util/failsafe/FailsafeManager.kt index 214512c3..ad57017b 100644 --- a/src/main/kotlin/org/cobalt/util/failsafe/FailsafeManager.kt +++ b/src/main/kotlin/org/cobalt/util/failsafe/FailsafeManager.kt @@ -1,6 +1,8 @@ package org.cobalt.util.failsafe import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents import org.cobalt.event.EventBus import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.TickEvent @@ -23,10 +25,10 @@ import org.lwjgl.glfw.GLFW object FailsafeManager { - var failsafes = mutableListOf() + var failsafes = CopyOnWriteArrayList() private val tempIgnored = ConcurrentHashMap.newKeySet() - private val ignoreGens = mutableMapOf() + private val ignoreGens = ConcurrentHashMap() private var triggeredFailsafe: Failsafe? = null private var queueTimer = 0 @@ -54,9 +56,22 @@ object FailsafeManager { fun initialize() { registerDefaultFailsafe() + registerWorldChangeIgnores() EventBus.register(this) } + private fun registerWorldChangeIgnores() { + ClientPlayConnectionEvents.JOIN.register { _, _, _ -> + val falseFlagOnWorldChange = listOf( + TeleportFailsafe, + SlotChangeFailsafe, + RotationFailsafe + ) + + falseFlagOnWorldChange.forEach { ignoreFailsafe(it) } + } + } + fun ignoreFailsafe(failsafe: Failsafe) { val gen = (ignoreGens[failsafe] ?: 0L) + 1L ignoreGens[failsafe] = gen diff --git a/src/main/kotlin/org/cobalt/util/failsafe/FailsafeUtils.kt b/src/main/kotlin/org/cobalt/util/failsafe/FailsafeUtils.kt deleted file mode 100644 index 4e6b179a..00000000 --- a/src/main/kotlin/org/cobalt/util/failsafe/FailsafeUtils.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.cobalt.util.failsafe - -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents -import org.cobalt.module.impl.failsafes.RotationFailsafe -import org.cobalt.module.impl.failsafes.SlotChangeFailsafe -import org.cobalt.module.impl.failsafes.TeleportFailsafe -import org.cobalt.module.type.Failsafe - -object FailsafeUtils { - fun init() { - ClientPlayConnectionEvents.JOIN.register { _, _, _ -> - val falseFlagOnWorldChange = listOf( - TeleportFailsafe, - SlotChangeFailsafe, - RotationFailsafe - ) - - falseFlagOnWorldChange.forEach { - FailsafeManager.ignoreFailsafe(it) - } - } - } -} diff --git a/src/main/kotlin/org/cobalt/util/render/skia/surface/GlSurface.kt b/src/main/kotlin/org/cobalt/util/render/skia/surface/GlSurface.kt index f6ca228c..2c522454 100644 --- a/src/main/kotlin/org/cobalt/util/render/skia/surface/GlSurface.kt +++ b/src/main/kotlin/org/cobalt/util/render/skia/surface/GlSurface.kt @@ -12,9 +12,7 @@ import org.lwjgl.opengl.GL33C internal class GlSurface : SkiaSurface { - private var context: DirectContext? = null - private var renderTarget: BackendRenderTarget? = null - private var surface: Surface? = null + private val skija = SkijaSurfaceCache() private var fbo = 0 private var depthStencil = 0 @@ -39,8 +37,8 @@ internal class GlSurface : SkiaSurface { GlStateManager._viewport(0, 0, width, height) GL33C.glBindSampler(0, 0) - val directContext = context ?: DirectContext.makeGL().also { - context = it + val directContext = skija.context ?: DirectContext.makeGL().also { + skija.context = it } directContext.resetGLAll() @@ -99,16 +97,13 @@ internal class GlSurface : SkiaSurface { } private fun surfaceFor(width: Int, height: Int, textureId: Int): Surface { - val existing = surface + val existing = skija.surface if (existing != null && existing.width == width && existing.height == height && lastTextureId == textureId) { return existing } - surface?.close() - renderTarget?.close() - - val directContext = context ?: DirectContext.makeGL().also { context = it } + val directContext = skija.context ?: DirectContext.makeGL().also { skija.context = it } val target = BackendRenderTarget.makeGL(width, height, 0, 8, fbo, GL30C.GL_RGBA8) val created = Surface.wrapBackendRenderTarget( directContext, @@ -118,18 +113,14 @@ internal class GlSurface : SkiaSurface { ColorSpace.getSRGB() ) - renderTarget = target - surface = created + skija.replaceSurface(target, created) lastTextureId = textureId return created } override fun close() { - surface?.close() - surface = null - renderTarget?.close() - renderTarget = null + skija.close() if (depthStencil != 0) { GL30C.glDeleteRenderbuffers(depthStencil) @@ -140,9 +131,6 @@ internal class GlSurface : SkiaSurface { GlStateManager._glDeleteFramebuffers(fbo) fbo = 0 } - - context?.close() - context = null } } diff --git a/src/main/kotlin/org/cobalt/util/render/skia/surface/SkijaSurfaceCache.kt b/src/main/kotlin/org/cobalt/util/render/skia/surface/SkijaSurfaceCache.kt new file mode 100644 index 00000000..ff6b9dbc --- /dev/null +++ b/src/main/kotlin/org/cobalt/util/render/skia/surface/SkijaSurfaceCache.kt @@ -0,0 +1,31 @@ +package org.cobalt.util.render.skia.surface + +import io.github.humbleui.skija.BackendRenderTarget +import io.github.humbleui.skija.DirectContext +import io.github.humbleui.skija.Surface + +internal class SkijaSurfaceCache { + + var context: DirectContext? = null + var renderTarget: BackendRenderTarget? = null + private set + var surface: Surface? = null + private set + + fun replaceSurface(renderTarget: BackendRenderTarget, surface: Surface) { + this.surface?.close() + this.renderTarget?.close() + this.renderTarget = renderTarget + this.surface = surface + } + + fun close() { + surface?.close() + surface = null + renderTarget?.close() + renderTarget = null + context?.close() + context = null + } + +} diff --git a/src/main/kotlin/org/cobalt/util/render/skia/surface/VulkanSurface.kt b/src/main/kotlin/org/cobalt/util/render/skia/surface/VulkanSurface.kt index 6dce69b3..95f2228e 100644 --- a/src/main/kotlin/org/cobalt/util/render/skia/surface/VulkanSurface.kt +++ b/src/main/kotlin/org/cobalt/util/render/skia/surface/VulkanSurface.kt @@ -13,9 +13,7 @@ import org.lwjgl.vulkan.VK12.* internal class VulkanSurface : SkiaSurface { - private var context: DirectContext? = null - private var renderTarget: BackendRenderTarget? = null - private var surface: Surface? = null + private val skija = SkijaSurfaceCache() private var cachedWidth = 0 private var cachedHeight = 0 @@ -31,7 +29,7 @@ internal class VulkanSurface : SkiaSurface { val vkImage = vkTexture.vkImage() val vkFormat = gpuFormatToVkFormat(vkTexture.format) - val directContext = context ?: makeContext().also { context = it } + val directContext = skija.context ?: makeContext().also { skija.context = it } RenderSystem.getDevice().createCommandEncoder().submit() directContext.resetAll() @@ -87,15 +85,12 @@ internal class VulkanSurface : SkiaSurface { vkImage: Long, vkFormat: Int, ): Surface { - val existing = surface + val existing = skija.surface if (existing != null && cachedWidth == width && cachedHeight == height && cachedVkImage == vkImage) { return existing } - surface?.close() - renderTarget?.close() - val rt = BackendRenderTarget.makeVulkan( width, height, vkImage, @@ -116,8 +111,7 @@ internal class VulkanSurface : SkiaSurface { ColorSpace.getSRGB(), ) - renderTarget = rt - surface = surf + skija.replaceSurface(rt, surf) cachedWidth = width cachedHeight = height cachedVkImage = vkImage @@ -126,14 +120,7 @@ internal class VulkanSurface : SkiaSurface { } override fun close() { - surface?.close() - surface = null - - renderTarget?.close() - renderTarget = null - - context?.close() - context = null + skija.close() } companion object { diff --git a/src/main/kotlin/org/cobalt/util/scheduling/TickScheduler.kt b/src/main/kotlin/org/cobalt/util/scheduling/TickScheduler.kt index a05b5425..52e76cfd 100644 --- a/src/main/kotlin/org/cobalt/util/scheduling/TickScheduler.kt +++ b/src/main/kotlin/org/cobalt/util/scheduling/TickScheduler.kt @@ -1,6 +1,6 @@ package org.cobalt.util.scheduling -import java.util.PriorityQueue +import java.util.concurrent.PriorityBlockingQueue import org.cobalt.event.EventBus import org.cobalt.event.annotation.SubscribeEvent import org.cobalt.event.impl.TickEvent @@ -8,7 +8,8 @@ import org.cobalt.event.impl.TickEvent object TickScheduler { private var currentTick: Long = 0 - private val taskQueue = PriorityQueue( + private val taskQueue = PriorityBlockingQueue( + 11, Comparator.comparingLong(ScheduledTask::executeTick) ) diff --git a/src/main/kotlin/org/cobalt/util/web/ResourceUtils.kt b/src/main/kotlin/org/cobalt/util/web/ResourceUtils.kt index 38d103ac..4b0c9eb9 100644 --- a/src/main/kotlin/org/cobalt/util/web/ResourceUtils.kt +++ b/src/main/kotlin/org/cobalt/util/web/ResourceUtils.kt @@ -6,7 +6,6 @@ import java.io.InputStream import java.net.HttpURLConnection import java.net.URI import java.nio.file.Files -import kotlinx.coroutines.runBlocking import org.cobalt.Cobalt object ResourceUtils { @@ -16,9 +15,7 @@ object ResourceUtils { val trimmedPath = location.trim() return if (trimmedPath.startsWith("http")) { - runBlocking { - getInputStream(trimmedPath).readBytes() - } + getInputStream(trimmedPath).use { it.readBytes() } } else { val file = File(trimmedPath)