From ab10032dc8797bcbe6fe3857547c05a802cdd0af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 01:04:16 +0800 Subject: [PATCH 01/16] start --- src-theme/src/integration/types.ts | 1 + .../config/types/MultiChooseListValue.kt | 15 +++++- .../config/types/nesting/Configurable.kt | 35 ++++++++++---- .../utils/combat/TargetTracker.kt | 46 +++++++++++-------- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src-theme/src/integration/types.ts b/src-theme/src/integration/types.ts index 4d34c3e84af..c13c6f9e890 100644 --- a/src-theme/src/integration/types.ts +++ b/src-theme/src/integration/types.ts @@ -144,6 +144,7 @@ export interface ChooseSetting extends Setting { export interface MultiChooseSetting extends Setting { choices: string[]; canBeNone: boolean; + isOrderSensitive: boolean; } export interface ListSetting extends Setting { diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/MultiChooseListValue.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/MultiChooseListValue.kt index 01640c46880..712a58f1466 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/MultiChooseListValue.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/MultiChooseListValue.kt @@ -23,12 +23,14 @@ import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonPrimitive import net.ccbluex.liquidbounce.config.gson.stategies.Exclude +import java.util.SequencedSet import java.util.TreeMap class MultiChooseListValue( name: String, /** - * Enabled values. A mutable and unordered [Set]. + * Enabled values in [MutableSet]. + * Its order is determined by the implementation of [MutableSet]. */ value: MutableSet, /** @@ -40,6 +42,11 @@ class MultiChooseListValue( * Can deselect all values or enable at least one */ @Exclude val canBeNone: Boolean = true, + + /** + * Determines whether the order of enabled values matters. + */ + @Exclude val isOrderSensitive: Boolean = false, ) : Value>( name, defaultValue = value, @@ -58,6 +65,12 @@ class MultiChooseListValue( } } + if (isOrderSensitive) { + require(value is SequencedSet) { + "The value must be a SequencedSet (e.g. TreeSet or LinkedHashSet) when isOrderSensitive is true." + } + } + value.retainAll(choices) } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/nesting/Configurable.kt b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/nesting/Configurable.kt index c617403541d..b4d36de7bbb 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/config/types/nesting/Configurable.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/config/types/nesting/Configurable.kt @@ -153,7 +153,7 @@ open class Configurable( return output.toTypedArray() } - fun getContainedValuesRecursivelyInternal(output: MutableList>) { + protected fun getContainedValuesRecursivelyInternal(output: MutableList>) { for (currentValue in this.inner) { if (currentValue is ToggleableConfigurable) { output.add(currentValue) @@ -392,14 +392,25 @@ open class Configurable( choices: EnumSet = EnumSet.allOf(T::class.java), canBeNone: Boolean = true, ) where T : Enum, T : NamedChoice = - multiEnumChoice(name, default, choices as Set, canBeNone) + multiEnumChoice(name, default, choices, canBeNone, isOrderSensitive = false) + + inline fun multiEnumChoice( + name: String, + default: SequencedSet, + choices: EnumSet = EnumSet.allOf(T::class.java), + canBeNone: Boolean = true, + ) where T : Enum, T : NamedChoice = + multiEnumChoice(name, default, choices, canBeNone, isOrderSensitive = true) fun multiEnumChoice( name: String, default: MutableSet, choices: Set, canBeNone: Boolean, - ) = MultiChooseListValue(name, default, choices, canBeNone).apply { this@Configurable.inner.add(this) } + isOrderSensitive: Boolean, + ) = MultiChooseListValue(name, default, choices, canBeNone, isOrderSensitive).apply { + this@Configurable.inner.add(this) + } inline fun enumChoice(name: String, default: T): ChooseListValue where T : Enum, T : NamedChoice = enumChoice(name, default, EnumSet.allOf(T::class.java)) @@ -550,15 +561,21 @@ open class Configurable( } ValueType.MULTI_CHOOSE -> { - val value = valueObject["value"].asJsonArray.mapTo(sortedSetOf()) { it.asString.asNamedChoice() } - val choices = valueObject["choices"].asJsonArray.mapTo(linkedSetOf()) { it.asString.asNamedChoice() } - val canBeNone = when (val json = valueObject["canBeNone"]) { - null, is JsonNull -> true // default = true + fun parseBoolean(key: String, default: Boolean) = when (val json = valueObject[key]) { + null, is JsonNull -> default is JsonPrimitive, is JsonArray -> json.asBoolean - else -> error("Unexpected JSON (${json.javaClass}): $json, should be boolean") + else -> error("Unexpected JSON value (${json.javaClass}): $json, should be boolean") } - multiEnumChoice(name, value, choices, canBeNone) + val canBeNone = parseBoolean(key = "canBeNone", default = true) + val isOrderSensitive = parseBoolean(key = "isOrderSensitive", default = false) + + val value = valueObject["value"].asJsonArray.mapTo( + if (isOrderSensitive) sortedSetOf() else linkedSetOf() + ) { it.asString.asNamedChoice() } + val choices = valueObject["choices"].asJsonArray.mapTo(linkedSetOf()) { it.asString.asNamedChoice() } + + multiEnumChoice(name, default = value, choices, canBeNone, isOrderSensitive) } else -> error("Unsupported type: $type") diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt index 6c457891dd8..2200b742a69 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt @@ -19,6 +19,8 @@ package net.ccbluex.liquidbounce.utils.combat import it.unimi.dsi.fastutil.objects.ObjectArrayList +import net.ccbluex.fastutil.mapToArray +import net.ccbluex.fastutil.objectLinkedSetOf import net.ccbluex.liquidbounce.config.types.nesting.Configurable import net.ccbluex.liquidbounce.config.types.NamedChoice import net.ccbluex.liquidbounce.config.types.RangedValue @@ -28,6 +30,7 @@ import net.ccbluex.liquidbounce.utils.client.* import net.ccbluex.liquidbounce.utils.entity.getActualHealth import net.ccbluex.liquidbounce.utils.entity.squaredBoxedDistanceTo import net.ccbluex.liquidbounce.utils.math.sq +import net.ccbluex.liquidbounce.utils.sorting.ComparatorChain import net.minecraft.entity.LivingEntity import net.minecraft.entity.mob.Angerable import net.minecraft.entity.mob.HostileEntity @@ -93,7 +96,17 @@ open class TargetSelector( private val range = rangeValue.register(this) private val fov by float("FOV", 180f, 0f..180f) private val hurtTime by int("HurtTime", 10, 0..10) - private val priority by enumChoice("Priority", defaultPriority) + + @Suppress("unused", "UnusedPrivateProperty") + private val priority by multiEnumChoice( + name = "Priority", + default = objectLinkedSetOf(TargetPriority.TYPE, defaultPriority), + canBeNone = false, + ).onChanged { set -> + comparator = ComparatorChain(comparisonFunctions = set.mapToArray { it.comparator }) + } + + private var comparator: Comparator = TargetPriority.TYPE.comparator /** * Counts available targets. @@ -118,13 +131,7 @@ open class TargetSelector( return entities } - entities.sortWith( - if (priority == TargetPriority.DISTANCE) { - COMPARATOR_BY_TYPE.thenComparing(TargetPriority.DISTANCE.comparator) - } else { - COMPARATOR_BY_TYPE.thenComparing(priority.comparator).thenComparing(TargetPriority.DISTANCE.comparator) - } - ) + entities.sortWith(this.comparator) // Update max distance squared closestSquaredEnemyDistance = entities.minOf { it.squaredBoxedDistanceTo(player) } @@ -176,15 +183,6 @@ open class TargetSelector( } -private val COMPARATOR_BY_TYPE: Comparator = Comparator.comparingInt { entity -> - when (entity) { - is PlayerEntity -> 0 - is HostileEntity -> 1 - is Angerable if entity.angryAt == player.uuid -> 2 - else -> Int.MAX_VALUE - } -} - enum class TargetPriority(override val choiceName: String, val comparator: Comparator) : NamedChoice { /** * Lowest health first @@ -194,7 +192,7 @@ enum class TargetPriority(override val choiceName: String, val comparator: Compa /** * Closest to you first */ - DISTANCE("Distance", Comparator.comparingDouble { it.squaredBoxedDistanceTo(player) }), + DISTANCE("Distance", Comparator.comparingDouble(player::squaredBoxedDistanceTo)), /** * Closest to your crosshair first @@ -210,4 +208,16 @@ enum class TargetPriority(override val choiceName: String, val comparator: Compa * Oldest entity first */ AGE("Age", Comparator.comparingInt { -it.age }), + + /** + * Player first + */ + TYPE("Type", Comparator.comparingInt { entity -> + when (entity) { + is PlayerEntity -> 0 + is HostileEntity -> 1 + is Angerable if entity.angryAt == player.uuid -> 2 + else -> Int.MAX_VALUE + } + }) } From ee1957499bf47e729655ab106528cb9dd9bf12cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 01:15:42 +0800 Subject: [PATCH 02/16] OOP --- .../utils/combat/TargetTracker.kt | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt b/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt index 2200b742a69..50339c9da2a 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/utils/combat/TargetTracker.kt @@ -19,7 +19,6 @@ package net.ccbluex.liquidbounce.utils.combat import it.unimi.dsi.fastutil.objects.ObjectArrayList -import net.ccbluex.fastutil.mapToArray import net.ccbluex.fastutil.objectLinkedSetOf import net.ccbluex.liquidbounce.config.types.nesting.Configurable import net.ccbluex.liquidbounce.config.types.NamedChoice @@ -103,10 +102,10 @@ open class TargetSelector( default = objectLinkedSetOf(TargetPriority.TYPE, defaultPriority), canBeNone = false, ).onChanged { set -> - comparator = ComparatorChain(comparisonFunctions = set.mapToArray { it.comparator }) + comparator = ComparatorChain(comparisonFunctions = set.toTypedArray()) } - private var comparator: Comparator = TargetPriority.TYPE.comparator + private var comparator: Comparator = TargetPriority.TYPE /** * Counts available targets. @@ -183,41 +182,60 @@ open class TargetSelector( } -enum class TargetPriority(override val choiceName: String, val comparator: Comparator) : NamedChoice { +enum class TargetPriority(override val choiceName: String) : NamedChoice, Comparator { /** * Lowest health first */ - HEALTH("Health", Comparator.comparingDouble { it.getActualHealth().toDouble() }), + HEALTH("Health") { + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + o1.getActualHealth() compareTo o2.getActualHealth() + }, /** * Closest to you first */ - DISTANCE("Distance", Comparator.comparingDouble(player::squaredBoxedDistanceTo)), + DISTANCE("Distance") { + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + o1.squaredBoxedDistanceTo(player) compareTo o2.squaredBoxedDistanceTo(player) + }, /** * Closest to your crosshair first */ - DIRECTION("Direction", Comparator.comparingDouble { RotationUtil.crosshairAngleToEntity(it).toDouble() }), + DIRECTION("Direction") { + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + RotationUtil.crosshairAngleToEntity(o1) compareTo RotationUtil.crosshairAngleToEntity(o2) + }, /** * With the lowest hurt time first */ - HURT_TIME("HurtTime", Comparator.comparingInt { it.hurtTime }), + HURT_TIME("HurtTime") { + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + o1.hurtTime compareTo o2.hurtTime + }, /** * Oldest entity first */ - AGE("Age", Comparator.comparingInt { -it.age }), + AGE("Age") { + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + o2.age compareTo o1.age + }, /** * Player first */ - TYPE("Type", Comparator.comparingInt { entity -> - when (entity) { - is PlayerEntity -> 0 - is HostileEntity -> 1 - is Angerable if entity.angryAt == player.uuid -> 2 - else -> Int.MAX_VALUE - } - }) + TYPE("Type") { + private fun weight(entity: LivingEntity): Int = + when (entity) { + is PlayerEntity -> 0 + is HostileEntity -> 1 + is Angerable if entity.angryAt == player.uuid -> 2 + else -> Int.MAX_VALUE + } + + override fun compare(o1: LivingEntity, o2: LivingEntity): Int = + weight(o1) compareTo weight(o2) + }, } From 19f51db8d228a8c77e7a88fd80c0a5da64e54bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 01:22:31 +0800 Subject: [PATCH 03/16] fix some issue in theme code --- .../clickgui/setting/MultiChooseSetting.svelte | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte index b3de5d28f18..3955f997ee1 100644 --- a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte +++ b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte @@ -1,5 +1,5 @@ @@ -65,9 +67,7 @@ class="choice" class:active={cSetting.value.includes(choice)} class:error={errorValue === choice} - on:click={() => { - handleChange(choice) - }} + on:click={() => handleChange(choice)} > {$spaceSeperatedNames ? convertToSpacedString(choice) : choice} From 7b003368cfa1f79aa56e858c8903988bbee8d8b0 Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:53:05 +0800 Subject: [PATCH 04/16] Nametag equipment support --- .../modules/render/nametags/ModuleNametags.kt | 5 ++ .../module/modules/render/nametags/Nametag.kt | 30 ++---------- .../render/nametags/NametagShowOptions.kt | 48 +++++++++++++++++++ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/ModuleNametags.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/ModuleNametags.kt index 46e2023eec5..e0e418bed77 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/ModuleNametags.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/ModuleNametags.kt @@ -38,6 +38,11 @@ import org.joml.Vector2fc */ object ModuleNametags : ClientModule("Nametags", Category.RENDER) { internal val show by multiEnumChoice("Show", NametagShowOptions.entries) + + init { + tree(NametagEquipment) + } + val scale by float("Scale", 2F, 0.25F..4F) private val maximumDistance by float("MaximumDistance", 128F, 1F..512F) diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/Nametag.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/Nametag.kt index 8d21dd3edc5..872db2f045e 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/Nametag.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/Nametag.kt @@ -19,11 +19,9 @@ package net.ccbluex.liquidbounce.features.module.modules.render.nametags import net.ccbluex.liquidbounce.render.engine.type.Vec3 -import net.ccbluex.liquidbounce.utils.client.isOlderThanOrEqual1_8 import net.ccbluex.liquidbounce.utils.entity.interpolateCurrentPosition import net.ccbluex.liquidbounce.utils.render.WorldToScreen import net.minecraft.entity.Entity -import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.LivingEntity import net.minecraft.item.ItemStack import net.minecraft.text.Text @@ -43,7 +41,11 @@ class Nametag private constructor( var screenPos: Vec3? = null private set - constructor(entity: LivingEntity) : this(entity, NametagTextFormatter(entity).format(), createItemList(entity)) + constructor(entity: LivingEntity) : this( + entity, + NametagTextFormatter(entity).format(), + NametagEquipment.createItemList(entity), + ) fun calculateScreenPos(tickDelta: Float) { val nametagPos = entity.interpolateCurrentPosition(tickDelta) @@ -52,27 +54,5 @@ class Nametag private constructor( screenPos = WorldToScreen.calculateScreenPos(nametagPos) } - companion object { - - /** - * Creates a list of items that should be rendered above the name tag. Currently, it is the item in main hand, - * the item in off-hand (as long as it exists) and the armor items. - */ - private fun createItemList(entity: LivingEntity): List { - return buildList(6) { - this += entity.getEquippedStack(EquipmentSlot.MAINHAND) - this += entity.getEquippedStack(EquipmentSlot.HEAD) - this += entity.getEquippedStack(EquipmentSlot.CHEST) - this += entity.getEquippedStack(EquipmentSlot.LEGS) - this += entity.getEquippedStack(EquipmentSlot.FEET) - - if (!isOlderThanOrEqual1_8) { - this += entity.getEquippedStack(EquipmentSlot.OFFHAND) - } - } - } - - } - } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagShowOptions.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagShowOptions.kt index bcb6354b7e3..5336a653043 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagShowOptions.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagShowOptions.kt @@ -1,7 +1,14 @@ package net.ccbluex.liquidbounce.features.module.modules.render.nametags +import net.ccbluex.fastutil.mapToArray +import net.ccbluex.fastutil.objectLinkedSetOf import net.ccbluex.liquidbounce.config.types.NamedChoice +import net.ccbluex.liquidbounce.config.types.nesting.ToggleableConfigurable +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemStack +// TODO: Split this into detailed configuration internal enum class NametagShowOptions( override val choiceName: String ) : NamedChoice { @@ -15,3 +22,44 @@ internal enum class NametagShowOptions( fun isShowing() = this in ModuleNametags.show } + +internal object NametagEquipment : ToggleableConfigurable(ModuleNametags, "Equipment", true) { + + private val slots by multiEnumChoice( + "Slots", + objectLinkedSetOf( + NEquipmentSlot.MAINHAND, NEquipmentSlot.HEAD, NEquipmentSlot.CHEST, + NEquipmentSlot.LEGS, NEquipmentSlot.FEET, NEquipmentSlot.OFFHAND, + ), + ) + private val skipEmptySlot by boolean("SkipEmptySlot", true) + + /** + * Creates a list of items that should be rendered above the name tag. + */ + fun createItemList(entity: LivingEntity): List { + val stacks = slots.mapToArray { + entity.getEquippedStack(it.slot) + } + + return if (skipEmptySlot) { + stacks.filterNot { it.isEmpty } + } else { + stacks.asList() + } + } + + private enum class NEquipmentSlot( + override val choiceName: String, + val slot: EquipmentSlot, + ) : NamedChoice { + MAINHAND("Mainhand", EquipmentSlot.MAINHAND), + OFFHAND("Offhand", EquipmentSlot.OFFHAND), + FEET("Feet", EquipmentSlot.FEET), + LEGS("Legs", EquipmentSlot.LEGS), + CHEST("Chest", EquipmentSlot.CHEST), + HEAD("Head", EquipmentSlot.HEAD), + BODY("Body", EquipmentSlot.BODY), + SADDLE("Saddle", EquipmentSlot.SADDLE), + } +} From 8743a959fc284ae23af685ffed428d5627a4da5a Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:25:01 +0800 Subject: [PATCH 05/16] frontend --- src-theme/src/integration/util.ts | 8 ++ .../setting/MultiChooseSetting.svelte | 78 ++++++++++++++++--- .../render/nametags/NametagRenderer.kt | 2 +- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src-theme/src/integration/util.ts b/src-theme/src/integration/util.ts index 055d4805e0d..3b59c49e876 100644 --- a/src-theme/src/integration/util.ts +++ b/src-theme/src/integration/util.ts @@ -37,6 +37,14 @@ export function intToRgba(value: number): number[] { return [red, green, blue, alpha]; } +export const swap = (array: any[], i: number, j: number) => { + if (i < 0 || i >= array.length || j < 0 || j >= array.length) return; + + const it = array[i]; + array[i] = array[j]; + array[j] = it; +} + export const getHashParams = (): URLSearchParams => { const hash = window.location.hash.split('?')[1] || ''; return new URLSearchParams(hash); diff --git a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte index 3955f997ee1..efa58045a9a 100644 --- a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte +++ b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte @@ -5,6 +5,8 @@ import {convertToSpacedString, spaceSeperatedNames} from "../../../theme/theme_config"; import ExpandArrow from "./common/ExpandArrow.svelte"; import {setItem} from "../../../integration/persistent_storage"; + import {flip} from "svelte/animate"; + import {swap} from "../../../integration/util"; export let setting: ModuleSetting; export let path: string; @@ -17,8 +19,31 @@ const dispatch = createEventDispatcher(); - function handleChange(v: string) { - if (cSetting.value.includes(v)) { + const isEnabled = (choice: string) => cSetting.value.includes(choice); + + $: choices = (() => { + if (cSetting.isOrderSensitive) { + return [...cSetting.value, ...cSetting.choices.filter(it => !isEnabled(it))]; + } else { + return cSetting.value; + } + })(); + + let hoveringChoice = -1; + + const moveEnabledChoice = (i: number, j: number) => { + swap(cSetting.value, i, j); + cSetting.value = cSetting.value; + fireChange(); + } + + const fireChange = () => { + setting = {...cSetting}; + dispatch("change"); + }; + + const handleChange = (v: string) => { + if (isEnabled(v)) { const filtered = cSetting.value.filter(item => item !== v); if (filtered.length === 0 && !cSetting.canBeNone) { @@ -36,9 +61,8 @@ cSetting.value = [...cSetting.value, v]; } - setting = {...cSetting}; - dispatch("change"); - } + fireChange(); + }; let expanded = localStorage.getItem(thisPath) === "true"; @@ -62,14 +86,33 @@ {#if expanded}
- {#each cSetting.choices as choice} + {#each choices as choice, i (choice)}
handleChange(choice)} + animate:flip={{duration: 200}} + on:mouseenter={() => hoveringChoice = i} + on:mouseleave={() => hoveringChoice = -1} > - {$spaceSeperatedNames ? convertToSpacedString(choice) : choice} + + {#if cSetting.isOrderSensitive && i !== 0 && isEnabled(choice)} + moveEnabledChoice(i, i - 1)}> + < + + {/if} + + + handleChange(choice)}> + {$spaceSeperatedNames ? convertToSpacedString(choice) : choice} + + + + {#if cSetting.isOrderSensitive&& i !== choices.length - 1 && isEnabled(choice) && isEnabled(choices[i + 1])} + moveEnabledChoice(i, i + 1)}> + > + + {/if}
{/each}
@@ -111,7 +154,22 @@ &.active { background-color: rgba($accent-color, 0.1); - color: $accent-color; + .choice-name { + color: $accent-color; + } + } + + .choice-action { + color: $clickgui-text-dimmed-color; + transition: width 200ms ease-in-out; + + &:hover { + color: $accent-color; + } + + &.hidden { + width: 0; + } } } diff --git a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagRenderer.kt b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagRenderer.kt index d039b9443f5..5b133aa9f62 100644 --- a/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagRenderer.kt +++ b/src/main/kotlin/net/ccbluex/liquidbounce/features/module/modules/render/nametags/NametagRenderer.kt @@ -30,7 +30,7 @@ import org.joml.Vector2f private const val NAMETAG_PADDING: Int = 15 internal fun GUIRenderEnvironment.drawNametag(nametag: Nametag, pos: Vec3) { - if (NametagShowOptions.ITEMS.isShowing()) { + if (NametagShowOptions.ITEMS.isShowing() && nametag.items.isNotEmpty()) { val currentItemStackRenderer = if (NametagShowOptions.ITEM_INFO.isShowing()) { if (nametag.entity === player) { ItemStackListRenderer.SingleItemStackRenderer.All From 4c50b17a2c175bf265e3141409deea00bbf94d7c Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:57:13 +0800 Subject: [PATCH 06/16] animation --- .../clickgui/setting/MultiChooseSetting.svelte | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte index efa58045a9a..05eccc8a7c1 100644 --- a/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte +++ b/src-theme/src/routes/clickgui/setting/MultiChooseSetting.svelte @@ -1,7 +1,7 @@