From cabac7887d1b93a93795a5f053fc7b27273d7304 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:00:06 +0100 Subject: [PATCH 01/16] the start of a rewrite --- LICENSE.md => LICENSE | 0 build.gradle.kts | 69 +- gradle.properties | 11 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 6 +- .../scaleme/{Scaleme.java => ScaleMe.java} | 6 +- .../scaleme/client/ScalemeClient.java | 23 - .../scaleme/client/command/PresetCommand.java | 411 ---------- .../scaleme/client/data/PlayerPreset.java | 169 ---- .../client/gui/ScaleMeManagerScreen.java | 169 ---- .../gui/components/PresetEditorComponent.java | 769 ------------------ .../components/PresetListEntryComponent.java | 269 ------ .../client/mixin/HeldItemRendererMixin.java | 126 --- .../client/mixin/LivingEntityMixin.java | 154 ---- .../mixin/LivingEntityRendererMixin.java | 88 -- .../scaleme/client/mixin/MixinInGameHud.java | 32 - .../client/mixin/PerspectiveMixin.java | 47 -- .../mixin/PlayerEntityRenderStateMixin.java | 38 - .../mixin/PlayerEntityRendererMixin.java | 154 ---- .../mixin/VillagerEntityRenderStateMixin.java | 24 - .../mixin/VillagerEntityRendererMixin.java | 48 -- .../scaleme/client/util/HypixelDetector.java | 247 ------ .../scaleme/client/util/HypixelNpcUtil.java | 37 - .../util/PlayerEntityRenderStateAccessor.java | 13 - .../client/util/PlayerPresetManager.java | 493 ----------- .../client/util/PlayerUUIDResolver.java | 253 ------ .../scaleme/client/util/ScaleConstants.java | 91 --- .../scaleme/client/util/ScaleManager.java | 49 -- .../scaleme/client/util/ScaleTransformer.java | 146 ---- .../VillagerEntityRenderStateAccessor.java | 8 - src/main/resources/fabric.mod.json | 2 +- stonecutter.gradle.kts | 2 +- versions/1.21.10/gradle.properties | 5 +- versions/1.21.11/gradle.properties | 5 +- versions/1.21.5/gradle.properties | 10 - versions/1.21.8/gradle.properties | 10 - 36 files changed, 45 insertions(+), 3941 deletions(-) rename LICENSE.md => LICENSE (100%) rename src/main/java/com/github/kd_gaming1/scaleme/{Scaleme.java => ScaleMe.java} (63%) delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/ScalemeClient.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/command/PresetCommand.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/data/PlayerPreset.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/gui/ScaleMeManagerScreen.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetEditorComponent.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetListEntryComponent.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/HeldItemRendererMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityRendererMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/MixinInGameHud.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PerspectiveMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRenderStateMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRendererMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRenderStateMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRendererMixin.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelDetector.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelNpcUtil.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerEntityRenderStateAccessor.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerPresetManager.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerUUIDResolver.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleConstants.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleManager.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleTransformer.java delete mode 100644 src/main/java/com/github/kd_gaming1/scaleme/client/util/VillagerEntityRenderStateAccessor.java delete mode 100644 versions/1.21.5/gradle.properties delete mode 100644 versions/1.21.8/gradle.properties diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/build.gradle.kts b/build.gradle.kts index b35a44e..b38d070 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ -import org.gradle.kotlin.dsl.from - plugins { + `maven-publish` id("fabric-loom") id("me.modmuss50.mod-publish-plugin") } @@ -9,42 +8,42 @@ version = "${property("mod.version")}+${stonecutter.current.version}" base.archivesName = property("mod.id") as String repositories { - /** - * Restricts dependency search of the given [groups] to the [maven URL][url], - * improving the setup speed. - */ + mavenCentral() fun strictMaven(url: String, alias: String, vararg groups: String) = exclusiveContent { forRepository { maven(url) { name = alias } } filter { groups.forEach(::includeGroup) } } - strictMaven("https://www.cursemaven.com", "CurseForge", "curse.maven") strictMaven("https://api.modrinth.com/maven", "Modrinth", "maven.modrinth") - maven("https://maven.terraformersmc.com/") - maven("https://maven.wispforest.io/releases/") - maven("https://jitpack.io") - maven("https://repo.hypixel.net/repository/Hypixel/") maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") + exclusiveContent { + forRepository { + maven { + url = uri("https://maven.azureaaron.net/releases") + } + } + + filter { + includeGroup("net.azureaaron") + } + } } dependencies { minecraft("com.mojang:minecraft:${stonecutter.current.version}") - mappings("net.fabricmc:yarn:${property("deps.yarn_mappings")}:v2") + mappings(loom.officialMojangMappings()) modImplementation("net.fabricmc:fabric-loader:${property("deps.fabric_loader")}") modImplementation("net.fabricmc.fabric-api:fabric-api:${property("deps.fabric_api")}") modImplementation("maven.modrinth:midnightlib:${property("deps.midnightlib_version")}") include("maven.modrinth:midnightlib:${property("deps.midnightlib_version")}") - modImplementation("com.terraformersmc:modmenu:${property("deps.modmenu_version")}") - modImplementation("io.wispforest:owo-lib:${property("deps.owo_version")}") - - modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.1") + modImplementation("net.azureaaron:hm-api:${property("deps.hm_api_version")}") + include("net.azureaaron:hm-api:${property("deps.hm_api_version")}") - implementation("com.google.code.gson:gson:2.10.1") - implementation("org.apache.httpcomponents:httpclient:4.5.13") + modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.2") + modRuntimeOnly("maven.modrinth:modmenu:${property("deps.modmenu_version")}") } -// Add this mixin configuration block loom { decompilerOptions.named("vineflower") { options.put("mark-corresponding-synthetics", "1") // Adds names to lambdas - useful for mixins @@ -71,21 +70,21 @@ tasks { inputs.property("name", project.property("mod.name")) inputs.property("version", project.property("mod.version")) inputs.property("minecraft", project.property("mod.mc_dep")) - inputs.property("owo_version", project.property("deps.owo_version")) - inputs.property("midnightlib_version", project.property("deps.midnightlib_version")) + inputs.property("fabricloader", project.property("deps.fabric_loader")) + inputs.property("fabric_api", project.property("deps.fabric_api")) + inputs.property("hm_api", project.property("deps.hm_api_version")) val props = mapOf( "id" to project.property("mod.id"), "name" to project.property("mod.name"), "version" to project.property("mod.version"), "minecraft" to project.property("mod.mc_dep"), - "owo_version" to project.property("deps.owo_version"), - "midnightlib_version" to project.property("deps.midnightlib_version") + "fabricloader" to project.property("deps.fabric_loader"), + "fabric_api" to project.property("deps.fabric_api"), + "hm_api" to project.property("deps.hm_api_version"), ) - filesMatching("fabric.mod.json") { - expand(props) - } + filesMatching("fabric.mod.json") { expand(props) } } jar { @@ -97,22 +96,12 @@ tasks { // Builds the version into a shared folder in `build/libs/${mod version}/` register("buildAndCollect") { group = "build" - from( - remapJar.map { it.archiveFile }, - remapSourcesJar.map { it.archiveFile } - ) + from(remapJar.map { it.archiveFile }, remapSourcesJar.map { it.archiveFile }) into(rootProject.layout.buildDirectory.file("libs/${project.property("mod.version")}")) dependsOn("build") } } -stonecutter { - replacements.string(current.parsed >= "1.21.11") { - replace("Components", "UIComponents") - replace("Containers", "UIContainers") - } -} - publishMods { file = tasks.remapJar.map { it.archiveFile.get() } additionalFiles.from(tasks.remapSourcesJar.map { it.archiveFile.get() }) @@ -132,9 +121,6 @@ publishMods { requires { slug = "P7dR8mSH" // Fabric API } - requires { - slug = "ccKDOlHs" // OwO Lib - } optional { slug = "mOgUt4GM" // ModMenu } @@ -147,9 +133,6 @@ publishMods { requires { slug = "fabric-api" } - requires { - slug = "owo-lib" - } optional { slug = "modmenu" } diff --git a/gradle.properties b/gradle.properties index effd1fc..3f40efa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,18 +4,19 @@ org.gradle.parallel=true org.gradle.configuration-cache=false # Mod properties -mod.version=2.2.6 +mod.version=3.0.0-alpha.1 mod.group=com.github.kd_gaming1 mod.id=scaleme -mod.name=ScaleMe +mod.name=Scale Me # Global dependencies deps.fabric_loader=0.18.4 -deps.yarn_mappings=[VERSIONED] + +# Global dependencies deps.fabric_api=[VERSIONED] +deps.moulconfig_version=[VERSIONED] deps.midnightlib_version=[VERSIONED] -deps.modmenu_version=[VERSIONED] -deps.owo_version=[VERSIONED] +deps.hm_api_version=[VERSIONED] publish.modrinth=pEGu9f0K publish.curseforge=1378514 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2..37f78a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index fbd284c..dcee887 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,12 +8,12 @@ pluginManagement { } plugins { - id("dev.kikugie.stonecutter") version "0.8.3" + id("dev.kikugie.stonecutter") version "0.9-alpha.7" } stonecutter { create(rootProject) { - versions("1.21.5", "1.21.8", "1.21.10", "1.21.11") - vcsVersion = "1.21.5" + versions("1.21.10", "1.21.11") + vcsVersion = "1.21.10" } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/Scaleme.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java similarity index 63% rename from src/main/java/com/github/kd_gaming1/scaleme/Scaleme.java rename to src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 2234825..137be0b 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/Scaleme.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -1,15 +1,15 @@ package com.github.kd_gaming1.scaleme; -import net.fabricmc.api.ModInitializer; +import net.fabricmc.api.ClientModInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Scaleme implements ModInitializer { +public class ScaleMe implements ClientModInitializer { public static final String MOD_ID = "scaleme"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); @Override - public void onInitialize() { + public void onInitializeClient() { } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/ScalemeClient.java b/src/main/java/com/github/kd_gaming1/scaleme/client/ScalemeClient.java deleted file mode 100644 index 9284d2f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/ScalemeClient.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.kd_gaming1.scaleme.client; - -import com.github.kd_gaming1.scaleme.Scaleme; -import com.github.kd_gaming1.scaleme.client.command.PresetCommand; -import com.github.kd_gaming1.scaleme.client.util.ScaleManager; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import eu.midnightdust.lib.config.MidnightConfig; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; - -public class ScalemeClient implements ClientModInitializer { - - @Override - public void onInitializeClient() { - MidnightConfig.init(Scaleme.MOD_ID, ScaleMeConfig.class); - - ScaleManager.init(); - ClientTickEvents.END_CLIENT_TICK.register(client -> ScaleManager.tick()); - - PresetCommand.register(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/command/PresetCommand.java b/src/main/java/com/github/kd_gaming1/scaleme/client/command/PresetCommand.java deleted file mode 100644 index a39b020..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/command/PresetCommand.java +++ /dev/null @@ -1,411 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.command; - -import com.github.kd_gaming1.scaleme.Scaleme; -import com.github.kd_gaming1.scaleme.client.data.PlayerPreset; -import com.github.kd_gaming1.scaleme.client.gui.ScaleMeManagerScreen; -import com.github.kd_gaming1.scaleme.client.util.PlayerPresetManager; -import com.github.kd_gaming1.scaleme.client.util.PlayerUUIDResolver; -import com.github.kd_gaming1.scaleme.client.util.ScaleConstants; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import com.mojang.brigadier.arguments.FloatArgumentType; -import com.mojang.brigadier.arguments.StringArgumentType; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import eu.midnightdust.lib.config.MidnightConfig; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.client.MinecraftClient; -import net.minecraft.text.Text; - -import java.util.List; -import java.util.UUID; - -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; - -/** - * Command handler for ScaleMe preset management and GUI access. - *

- * Available commands: - *

- */ -public class PresetCommand { - - public static void register() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { - dispatcher.register(literal("scaleme") - .executes(PresetCommand::executeOpenConfig) - // Preset management commands - .then(literal("add") - .then(argument("player", StringArgumentType.string()) - .then(argument("scale", FloatArgumentType.floatArg( - ScaleConstants.MIN_SCALE, - ScaleConstants.MAX_SCALE)) - .executes(PresetCommand::executeAdd)))) - - .then(literal("remove") - .then(argument("player", StringArgumentType.string()) - .executes(PresetCommand::executeRemove))) - - .then(literal("list") - .executes(PresetCommand::executeList)) - - .then(literal("enable") - .then(argument("player", StringArgumentType.string()) - .executes(PresetCommand::executeEnable))) - - .then(literal("disable") - .then(argument("player", StringArgumentType.string()) - .executes(PresetCommand::executeDisable))) - - .then(literal("reload") - .executes(PresetCommand::executeReload)) - - .then(literal("info") - .then(argument("player", StringArgumentType.string()) - .executes(PresetCommand::executeInfo))) - - // Configuration commands - .then(literal("setdefault") - .then(argument("scale", FloatArgumentType.floatArg( - ScaleConstants.MIN_SCALE, - ScaleConstants.MAX_SCALE)) - .executes(PresetCommand::executeSetDefault))) - - .then(literal("setown") - .then(argument("scale", FloatArgumentType.floatArg( - ScaleConstants.MIN_SCALE, - ScaleConstants.MAX_SCALE)) - .executes(PresetCommand::executeSetOwn))) - - // GUI commands - .then(literal("config") - .executes(PresetCommand::executeOpenConfig)) - - .then(literal("manager") - .executes(PresetCommand::executeOpenManager)) - - .then(literal("gui") - .executes(PresetCommand::executeOpenManager)) - ); - }); - } - - // ===== Preset Management Commands ===== - - /** - * Adds a new preset for a player. - */ - private static int executeAdd(CommandContext ctx) throws CommandSyntaxException { - String playerName = StringArgumentType.getString(ctx, "player"); - float scale = FloatArgumentType.getFloat(ctx, "scale"); - - // Validate scale - if (!ScaleConstants.isValidScale(scale)) { - sendError(ctx, String.format("Scale must be between %.1f and %.1f", - ScaleConstants.MIN_SCALE, ScaleConstants.MAX_SCALE)); - return 0; - } - - // Resolve player UUID - UUID playerUUID = resolvePlayerUUID(playerName); - if (playerUUID == null) { - sendError(ctx, "Player '" + playerName + "' not found or never joined this server."); - return 0; - } - - // Check if preset already exists - String uuidString = playerUUID.toString(); - PlayerPreset existing = PlayerPresetManager.getPreset(uuidString); - if (existing != null) { - sendError(ctx, "Preset for player '" + playerName + "' already exists. Use remove first to overwrite."); - return 0; - } - - // Create and add preset - PlayerPreset preset = new PlayerPreset(uuidString, playerName, scale); - PlayerPresetManager.addOrUpdatePreset(preset); - - sendSuccess(ctx, String.format("Added preset for '%s' with scale %.2f", playerName, scale)); - return 1; - } - - /** - * Removes a preset for a player. - */ - private static int executeRemove(CommandContext ctx) throws CommandSyntaxException { - String playerName = StringArgumentType.getString(ctx, "player"); - - UUID playerUUID = resolvePlayerUUID(playerName); - if (playerUUID == null) { - sendError(ctx, "Player '" + playerName + "' not found."); - return 0; - } - - String uuidString = playerUUID.toString(); - boolean removed = PlayerPresetManager.removePreset(uuidString); - - if (removed) { - sendSuccess(ctx, "Removed preset for '" + playerName + "'."); - return 1; - } else { - sendError(ctx, "No preset found for '" + playerName + "'."); - return 0; - } - } - - /** - * Lists all presets. - */ - private static int executeList(CommandContext ctx) { - List presets = PlayerPresetManager.getAllPresets(); - - if (presets.isEmpty()) { - sendInfo(ctx, "No presets found."); - return 1; - } - - sendInfo(ctx, "Player Scaling Presets:"); - for (PlayerPreset preset : presets) { - String status = preset.enabled ? "§a✓" : "§c✗"; - String name = preset.getEffectiveDisplayName(); - String scaleColor = getScaleColor(preset.scale); - - sendInfo(ctx, String.format(" %s §f%s§r: %s%.2f§r", - status, name, scaleColor, preset.scale)); - } - - return 1; - } - - /** - * Enables a preset. - */ - private static int executeEnable(CommandContext ctx) throws CommandSyntaxException { - String playerName = StringArgumentType.getString(ctx, "player"); - - UUID playerUUID = resolvePlayerUUID(playerName); - if (playerUUID == null) { - sendError(ctx, "Player '" + playerName + "' not found."); - return 0; - } - - String uuidString = playerUUID.toString(); - boolean success = PlayerPresetManager.setPresetEnabled(uuidString, true); - - if (success) { - sendSuccess(ctx, "Enabled preset for '" + playerName + "'."); - return 1; - } else { - sendError(ctx, "No preset found for '" + playerName + "'."); - return 0; - } - } - - /** - * Disables a preset. - */ - private static int executeDisable(CommandContext ctx) throws CommandSyntaxException { - String playerName = StringArgumentType.getString(ctx, "player"); - - UUID playerUUID = resolvePlayerUUID(playerName); - if (playerUUID == null) { - sendError(ctx, "Player '" + playerName + "' not found."); - return 0; - } - - String uuidString = playerUUID.toString(); - boolean success = PlayerPresetManager.setPresetEnabled(uuidString, false); - - if (success) { - sendSuccess(ctx, "Disabled preset for '" + playerName + "'."); - return 1; - } else { - sendError(ctx, "No preset found for '" + playerName + "'."); - return 0; - } - } - - /** - * Reloads all presets from disk. - */ - private static int executeReload(CommandContext ctx) { - PlayerPresetManager.loadPresets(); - sendSuccess(ctx, "Reloaded all presets from disk."); - return 1; - } - - /** - * Shows detailed information about a preset. - */ - private static int executeInfo(CommandContext ctx) throws CommandSyntaxException { - String playerName = StringArgumentType.getString(ctx, "player"); - - UUID playerUUID = resolvePlayerUUID(playerName); - if (playerUUID == null) { - sendError(ctx, "Player '" + playerName + "' not found."); - return 0; - } - - String uuidString = playerUUID.toString(); - PlayerPreset preset = PlayerPresetManager.getPreset(uuidString); - - if (preset == null) { - sendError(ctx, "No preset found for '" + playerName + "'."); - return 0; - } - - sendInfo(ctx, "Preset Information for '" + playerName + "':"); - sendInfo(ctx, " UUID: " + preset.identifier); - sendInfo(ctx, " Display Name: " + preset.getEffectiveDisplayName()); - sendInfo(ctx, " Scale: " + preset.scale); - sendInfo(ctx, " Status: " + (preset.enabled ? "§aEnabled§r" : "§cDisabled§r")); - - return 1; - } - - // ===== Configuration Commands ===== - - /** - * Sets the default scale for other players. - */ - private static int executeSetDefault(CommandContext ctx) throws CommandSyntaxException { - float scale = FloatArgumentType.getFloat(ctx, "scale"); - - if (!ScaleConstants.isValidScale(scale)) { - sendError(ctx, String.format("Scale must be between %.1f and %.1f", - ScaleConstants.MIN_SCALE, ScaleConstants.MAX_SCALE)); - return 0; - } - - ScaleMeConfig.otherPlayersScale = ScaleConstants.clampScale(scale); - sendSuccess(ctx, String.format("Set default scale for other players to %.2f", scale)); - - return 1; - } - - /** - * Sets the scale for the player's own character. - */ - private static int executeSetOwn(CommandContext ctx) throws CommandSyntaxException { - float scale = FloatArgumentType.getFloat(ctx, "scale"); - - if (!ScaleConstants.isValidScale(scale)) { - sendError(ctx, String.format("Scale must be between %.1f and %.1f", - ScaleConstants.MIN_SCALE, ScaleConstants.MAX_SCALE)); - return 0; - } - - ScaleMeConfig.ownPlayerScale = ScaleConstants.clampScale(scale); - sendSuccess(ctx, String.format("Set your own player scale to %.2f", scale)); - - return 1; - } - - // ===== GUI Commands ===== - - /** - * Opens the configuration menu. - * Uses client.send() to delay opening until after chat closes. - */ - private static int executeOpenConfig(CommandContext ctx) { - MinecraftClient client = MinecraftClient.getInstance(); - - if (client.player == null) { - sendError(ctx, "You must be in-game to open the config menu."); - return 0; - } - - // Schedule GUI opening on next tick (after chat closes) - client.send(() -> { - try { - client.setScreen(MidnightConfig.getScreen(client.currentScreen, Scaleme.MOD_ID)); - } catch (Exception e) { - Scaleme.LOGGER.error("Failed to open config menu", e); - } - }); - - sendSuccess(ctx, "Opening configuration menu..."); - return 1; - } - - /** - * Opens the preset manager GUI. - * Uses client.send() to delay opening until after chat closes. - */ - private static int executeOpenManager(CommandContext ctx) { - MinecraftClient client = MinecraftClient.getInstance(); - - if (client.player == null) { - sendError(ctx, "You must be in-game to open the preset manager."); - return 0; - } - - // Schedule GUI opening on next tick (after chat closes) - client.send(() -> { - try { - client.setScreen(new ScaleMeManagerScreen()); - } catch (Exception e) { - Scaleme.LOGGER.error("Failed to open preset manager", e); - } - }); - - sendSuccess(ctx, "Opening preset manager..."); - return 1; - } - - // ===== Helper Methods ===== - - /** - * Resolves a player's UUID from their username. - */ - private static UUID resolvePlayerUUID(String playerName) { - return PlayerUUIDResolver.resolvePlayerUUID(playerName); - } - - /** - * Returns a color code for a scale value based on its magnitude. - */ - private static String getScaleColor(float scale) { - if (ScaleConstants.isTinyScale(scale)) { - return "§9"; // Blue for tiny - } else if (ScaleConstants.isLargeScale(scale)) { - return "§c"; // Red for large - } else { - return "§a"; // Green for normal - } - } - - /** - * Sends a success message to the player. - */ - private static void sendSuccess(CommandContext ctx, String message) { - ctx.getSource().sendFeedback(Text.literal("§a[ScaleMe] " + message)); - } - - /** - * Sends an error message to the player. - */ - private static void sendError(CommandContext ctx, String message) { - ctx.getSource().sendError(Text.literal("§c[ScaleMe] " + message)); - } - - /** - * Sends an informational message to the player. - */ - private static void sendInfo(CommandContext ctx, String message) { - ctx.getSource().sendFeedback(Text.literal("§b[ScaleMe] " + message)); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/data/PlayerPreset.java b/src/main/java/com/github/kd_gaming1/scaleme/client/data/PlayerPreset.java deleted file mode 100644 index 51cd0b5..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/data/PlayerPreset.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.data; - -import com.github.kd_gaming1.scaleme.client.util.PlayerUUIDResolver; -import com.github.kd_gaming1.scaleme.client.util.ScaleConstants; -import com.google.gson.annotations.SerializedName; - -import java.util.Objects; -import java.util.UUID; - -public class PlayerPreset { - - @SerializedName("identifier") - public String identifier; - - @SerializedName("friendlyName") - public String friendlyName; - - @SerializedName("scale") - public float scale; - - @SerializedName("enabled") - public boolean enabled; - - /** - * Creates a new player preset with validation. - * @param identifier The player identifier (UUID or username) - * @param friendlyName Optional display name (can be null) - * @param scale The scale factor - * @throws IllegalArgumentException if scale is out of range - */ - public PlayerPreset(String identifier, String friendlyName, float scale) { - // Allow empty identifier for draft presets - // Validation will occur during save operation - this.identifier = identifier != null ? identifier.trim() : ""; - this.friendlyName = (friendlyName != null && !friendlyName.trim().isEmpty()) - ? friendlyName.trim() : null; - - validateScale(scale); - this.scale = ScaleConstants.clampScale(scale); - this.enabled = true; - } - - /** - * Validates that this preset is ready to be saved. - * @throws IllegalArgumentException if preset is invalid - */ - public void validateForSave() { - validateIdentifier(this.identifier); - validateScale(this.scale); - } - - /** - * Checks if this preset is valid for saving. - * @return true if valid, false otherwise - */ - public boolean isValidForSave() { - try { - validateForSave(); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - // ===== Validation Helper Methods ===== - - public void setScaleValidated(float scale) { - validateScale(scale); - this.scale = ScaleConstants.clampScale(scale); - } - - public void setIdentifierValidated(String identifier) { - validateIdentifier(identifier); - this.identifier = identifier.trim(); - } - - // ===== Core Methods ===== - - public PlayerPreset copy() { - PlayerPreset copy = new PlayerPreset(this.identifier, this.friendlyName, this.scale); - copy.enabled = this.enabled; - return copy; - } - - public String getEffectiveDisplayName() { - return (friendlyName != null && !friendlyName.isEmpty()) - ? friendlyName - : identifier; - } - - public boolean matches(String id) { - return enabled && identifier.equalsIgnoreCase(id); - } - - public boolean matchesPlayer(UUID playerUUID, String playerName) { - if (!enabled || playerUUID == null) { - return false; - } - - if (isUUID()) { - try { - return UUID.fromString(identifier).equals(playerUUID); - } catch (IllegalArgumentException e) { - return false; - } - } - - return playerName != null && identifier.equalsIgnoreCase(playerName); - } - - public UUID resolveToUUID() { - return PlayerUUIDResolver.resolvePlayerUUID(this.identifier); - } - - // ===== Validation Methods ===== - - public boolean isUUID() { - return identifier != null && identifier.matches(ScaleConstants.UUID_REGEX); - } - - public boolean isUsername() { - return identifier != null && identifier.matches(ScaleConstants.USERNAME_REGEX); - } - - /** - * Validates that an identifier is not null and not empty. - * @param identifier The identifier to validate - * @throws IllegalArgumentException if identifier is invalid - */ - private void validateIdentifier(String identifier) { - if (identifier == null || identifier.trim().isEmpty()) { - throw new IllegalArgumentException(ScaleConstants.ERROR_NULL_IDENTIFIER); - } - } - - private void validateScale(float scale) { - if (!ScaleConstants.isValidScale(scale)) { - throw new IllegalArgumentException( - String.format(ScaleConstants.ERROR_INVALID_SCALE, - ScaleConstants.MIN_SCALE, - ScaleConstants.MAX_SCALE) - ); - } - } - - // ===== Object Methods ===== - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PlayerPreset that = (PlayerPreset) o; - return Float.compare(that.scale, scale) == 0 && - enabled == that.enabled && - Objects.equals(identifier, that.identifier) && - Objects.equals(friendlyName, that.friendlyName); - } - - @Override - public int hashCode() { - return Objects.hash(identifier, friendlyName, scale, enabled); - } - - @Override - public String toString() { - return String.format("PlayerPreset{identifier='%s', friendlyName='%s', scale=%.2f, enabled=%s}", - identifier, friendlyName, scale, enabled); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/ScaleMeManagerScreen.java b/src/main/java/com/github/kd_gaming1/scaleme/client/gui/ScaleMeManagerScreen.java deleted file mode 100644 index 68ac122..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/ScaleMeManagerScreen.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.gui; - -import com.github.kd_gaming1.scaleme.client.data.PlayerPreset; -import com.github.kd_gaming1.scaleme.client.gui.components.PresetEditorComponent; -import com.github.kd_gaming1.scaleme.client.gui.components.PresetListEntryComponent; -import com.github.kd_gaming1.scaleme.client.util.PlayerPresetManager; -import io.wispforest.owo.ui.base.BaseOwoScreen; -import io.wispforest.owo.ui.component.Components; -import io.wispforest.owo.ui.container.Containers; -import io.wispforest.owo.ui.container.FlowLayout; -import io.wispforest.owo.ui.container.ScrollContainer; -import io.wispforest.owo.ui.core.*; -import net.minecraft.text.Text; -import org.jetbrains.annotations.NotNull; -import eu.midnightdust.lib.config.MidnightConfig; -import com.github.kd_gaming1.scaleme.Scaleme; - -import java.util.ArrayList; -import java.util.List; - -public class ScaleMeManagerScreen extends BaseOwoScreen { - private FlowLayout presetList; - private ScrollContainer scrollContainer; - private PresetEditorComponent editorComponent; - private final List entryComponents = new ArrayList<>(); - - @Override - protected @NotNull OwoUIAdapter createAdapter() { - return OwoUIAdapter.create(this, Containers::verticalFlow); - } - - @Override - protected void build(FlowLayout rootComponent) { - rootComponent.horizontalAlignment(HorizontalAlignment.CENTER); - rootComponent.surface(Surface.VANILLA_TRANSLUCENT); - - // Title section - FlowLayout titleSection = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(10)); - - titleSection.child( - Components.label(Text.of("ScaleMe Manager")) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .color(Color.WHITE) - .shadow(true) - ); - titleSection.child( - Components.label(Text.of("Player-Specific Scaling")) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .color(Color.ofRgb(0xAAFFAA)) - .shadow(false) - .margins(Insets.top(2)) - ); - titleSection.child( - Components.label(Text.of("Use 'Config' for personal and global scaling settings")) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .color(Color.ofRgb(0x888888)) - .shadow(false) - .margins(Insets.top(2)) - ); - rootComponent.child(titleSection); - - // Main split layout - FlowLayout splitRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.fill(65)) - .gap(12) - .margins(Insets.horizontal(10)) - .horizontalAlignment(HorizontalAlignment.CENTER); - - FlowLayout leftColumn = Containers.verticalFlow(Sizing.fill(49), Sizing.fill(100)); - FlowLayout rightColumn = Containers.verticalFlow(Sizing.fill(49), Sizing.fill(100)); - splitRow.child(leftColumn); - splitRow.child(rightColumn); - rootComponent.child(splitRow); - - buildLeftColumn(leftColumn); - buildRightColumn(rightColumn); - - // Bottom button row - FlowLayout buttonRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(90), Sizing.content()) - .gap(8) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(15)); - - buttonRow.child(Components.button(Text.literal("New Preset"), button -> editorComponent.createNewPreset())); - buttonRow.child(Components.button(Text.literal("Config"), button -> { - if (client.player != null) { - client.setScreen(MidnightConfig.getScreen(client.currentScreen, Scaleme.MOD_ID)); - } - })); - buttonRow.child(Components.button(Text.literal("Refresh"), button -> refreshPresetList())); - buttonRow.child(Components.button(Text.literal("Done"), button -> this.close())); - - rootComponent.child(buttonRow); - } - - private void buildLeftColumn(FlowLayout leftColumn) { - leftColumn.child( - Components.label(Text.literal("Presets")) - .color(Color.WHITE) - .shadow(true) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(2, 8, 2, 2)) - ); - - this.presetList = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .padding(Insets.of(2)); - this.scrollContainer = Containers.verticalScroll(Sizing.fill(100), Sizing.fill(100), this.presetList); - leftColumn.child(this.scrollContainer); - - refreshPresetList(); - } - - private void buildRightColumn(FlowLayout rightColumn) { - rightColumn.child( - Components.label(Text.literal("Editor")) - .color(Color.WHITE) - .shadow(true) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .margins(Insets.of(2, 8, 2, 2)) - ); - - this.editorComponent = new PresetEditorComponent( - this::onPresetUpdated, - this::onPresetDeleted - ); - rightColumn.child(this.editorComponent); - } - - private void refreshPresetList() { - this.presetList.clearChildren(); - this.entryComponents.clear(); - - for (PlayerPreset preset : PlayerPresetManager.getAllPresets()) { - PresetListEntryComponent entry = new PresetListEntryComponent(preset); - //? if >=1.21.9 { - /*entry.mouseDown().subscribe((click, doubled) -> { - if (click.button() == 0) { - selectPreset(preset, entry); - return true; - } - return false; - }); - *///?} else { - entry.mouseDown().subscribe((mouseX, mouseY, button) -> { - if (button == 0) { - selectPreset(preset, entry); - return true; - } - return false; - }); - //?} - this.entryComponents.add(entry); - this.presetList.child(entry); - } - } - - private void selectPreset(PlayerPreset preset, PresetListEntryComponent entryComponent) { - editorComponent.editPreset(preset); - } - - private void onPresetUpdated(PlayerPreset preset) { - refreshPresetList(); - } - - private void onPresetDeleted(PlayerPreset preset) { - refreshPresetList(); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetEditorComponent.java b/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetEditorComponent.java deleted file mode 100644 index ae8d66c..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetEditorComponent.java +++ /dev/null @@ -1,769 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.gui.components; - -import com.github.kd_gaming1.scaleme.client.data.PlayerPreset; -import com.github.kd_gaming1.scaleme.client.util.PlayerPresetManager; -import com.github.kd_gaming1.scaleme.client.util.PlayerUUIDResolver; -import com.github.kd_gaming1.scaleme.client.util.ScaleConstants; -import io.wispforest.owo.ui.component.ButtonComponent; -import io.wispforest.owo.ui.component.Components; -import io.wispforest.owo.ui.component.LabelComponent; -import io.wispforest.owo.ui.component.TextBoxComponent; -import io.wispforest.owo.ui.component.SlimSliderComponent; -import io.wispforest.owo.ui.container.Containers; -import io.wispforest.owo.ui.container.FlowLayout; -import io.wispforest.owo.ui.container.ScrollContainer; -import io.wispforest.owo.ui.core.*; -import net.minecraft.client.MinecraftClient; -import net.minecraft.text.Text; -import net.minecraft.util.Formatting; - -import java.util.UUID; -import java.util.function.Consumer; - -public class PresetEditorComponent extends FlowLayout { - private static final float MIN_SCALE = 0.1f; - private static final float MAX_SCALE = 3.0f; - - private PlayerPreset currentPreset; - private Consumer onPresetUpdated; - private Consumer onPresetDeleted; - private boolean isNewPreset = false; - - // Form components - private TextBoxComponent identifierField; - private TextBoxComponent friendlyNameField; - private SlimSliderComponent scaleSlider; - private LabelComponent scaleValueLabel; - private ButtonComponent saveButton; - private ButtonComponent deleteButton; - private ButtonComponent enableToggleButton; - private ButtonComponent cancelButton; - - // UI components - private LabelComponent titleLabel; - private LabelComponent statusLabel; - private LabelComponent identifierInfoLabel; - private FlowLayout emptyStateContainer; - private ScrollContainer editorContainer; - private FlowLayout helpContainer; - private boolean isShowingEmptyState = true; - - // Validation state - private boolean isIdentifierValid = true; - private boolean hasUnsavedChanges = false; - private String originalIdentifier = null; - - public PresetEditorComponent(Consumer onPresetUpdated, Consumer onPresetDeleted) { - super(Sizing.fill(100), Sizing.fill(100), Algorithm.VERTICAL); - this.onPresetUpdated = onPresetUpdated; - this.onPresetDeleted = onPresetDeleted; - this.surface(Surface.flat(0x33424242)).padding(Insets.of(8)); - buildLayout(); - showEmptyState(); - } - - // --- Layout Building --- - - private void buildLayout() { - this.titleLabel = Components.label(Text.literal("Select a Preset")) - .color(Color.WHITE) - .shadow(true) - .horizontalTextAlignment(HorizontalAlignment.CENTER); - this.child(this.titleLabel); - - this.statusLabel = (LabelComponent) Components.label(Text.empty()) - .color(Color.ofRgb(0xCCCCCC)) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .margins(Insets.bottom(8)); - this.child(this.statusLabel); - - buildEmptyState(); - buildEditorContainer(); - } - - private void buildEmptyState() { - this.emptyStateContainer = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.expand()) - .gap(12) - .horizontalAlignment(HorizontalAlignment.CENTER) - .verticalAlignment(VerticalAlignment.CENTER); - - this.emptyStateContainer.child( - Components.label(Text.literal("📋")) - .color(Color.ofRgb(0x888888)) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .sizing(Sizing.content(), Sizing.content()) - ); - this.emptyStateContainer.child( - Components.label(Text.literal("Select a preset to edit")) - .color(Color.ofRgb(0xCCCCCC)) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .sizing(Sizing.content(), Sizing.content()) - ); - this.emptyStateContainer.child( - Components.label(Text.literal("or create a new one")) - .color(Color.ofRgb(0x888888)) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .sizing(Sizing.content(), Sizing.content()) - ); - this.child(this.emptyStateContainer); - } - - private void buildEditorContainer() { - FlowLayout mainContent = Containers.verticalFlow(Sizing.fill(95), Sizing.content()).gap(8); - buildHelpContainer(); - - FlowLayout formContainer = Containers.verticalFlow(Sizing.fill(100), Sizing.content()).gap(8); - - // Player Identifier Field - formContainer.child(createFieldSection( - "Player Identifier: *", - "Username (will be resolved automatically)", - "Enter the exact username of the player you want to scale", - identifier -> { - this.identifierField = identifier; - identifier.onChanged().subscribe(this::onIdentifierChanged); - } - )); - - // Display Name Field - formContainer.child(createFieldSection( - "Display Name:", - "Custom name (optional)", - "Leave empty to use the player identifier as display name", - friendlyName -> { - this.friendlyNameField = friendlyName; - friendlyName.onChanged().subscribe(this::onFriendlyNameChanged); - } - )); - - // Scale Slider Section - formContainer.child(createScaleSliderSection()); - - this.identifierInfoLabel = (LabelComponent) Components.label(Text.empty()) - .color(Color.ofRgb(0x888888)) - .shadow(false) - .sizing(Sizing.fill(100), Sizing.content()); - formContainer.child(this.identifierInfoLabel); - - mainContent.child(formContainer); - buildButtonSection(mainContent); - - this.editorContainer = Containers.verticalScroll(Sizing.fill(100), Sizing.expand(), mainContent); - } - - private FlowLayout createScaleSliderSection() { - FlowLayout section = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .surface(Surface.flat(0x22000000)) - .padding(Insets.of(8)); - - // Label row with current value - FlowLayout labelRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(4) - .verticalAlignment(VerticalAlignment.CENTER); - - labelRow.child( - Components.label(Text.literal("Scale: *")) - .color(Color.ofRgb(0xFFAB91)) - .shadow(false) - ); - - this.scaleValueLabel = (LabelComponent) Components.label(Text.literal("1.00")) - .color(Color.ofRgb(0x4CAF50)) - .shadow(false); - labelRow.child(this.scaleValueLabel); - - section.child(labelRow); - - // SlimSliderComponent - more minimal look - this.scaleSlider = Components.slimSlider(SlimSliderComponent.Axis.HORIZONTAL); - this.scaleSlider.sizing(Sizing.fill(100), Sizing.content()); - this.scaleSlider.min(0.0); - this.scaleSlider.max(1.0); - this.scaleSlider.stepSize(0.01); - this.scaleSlider.tooltipSupplier(value -> Text.literal(String.format("%.2f", mapSliderToScale(value)))); - this.scaleSlider.onChanged().subscribe(this::onScaleSliderChanged); - - section.child(this.scaleSlider); - - // Scale indicators - FlowLayout indicatorRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(4) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.top(4)); - - indicatorRow.child(Components.label(Text.literal("Size multiplier for the player:")).color(Color.ofRgb(0x888888))); - indicatorRow.child(Components.label(Text.literal("Tiny")).color(Color.ofRgb(0x888888))); - indicatorRow.child(Components.label(Text.literal("(0.1)")).color(Color.ofRgb(0x666666))); - indicatorRow.child(Components.label(Text.literal("Normal")).color(Color.ofRgb(0x4CAF50))); - indicatorRow.child(Components.label(Text.literal("(1.0)")).color(Color.ofRgb(0x4CAF50))); - indicatorRow.child(Components.label(Text.literal("Large")).color(Color.ofRgb(0xFF5722))); - indicatorRow.child(Components.label(Text.literal("(3.0)")).color(Color.ofRgb(0xFF5722))); - - section.child(indicatorRow); - - return section; - } - - // Scale mapping functions - private double mapScaleToSlider(float scale) { - return (scale - MIN_SCALE) / (MAX_SCALE - MIN_SCALE); - } - - private float mapSliderToScale(double sliderValue) { - float scale = (float) (MIN_SCALE + sliderValue * (MAX_SCALE - MIN_SCALE)); - return Math.round(scale * 100f) / 100f; // Round to 2 decimal places - } - - private void buildHelpContainer() { - this.helpContainer = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .surface(Surface.flat(0x22004488)) - .padding(Insets.of(8)) - .margins(Insets.bottom(8)); - - FlowLayout headerRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(6) - .verticalAlignment(VerticalAlignment.CENTER); - - headerRow.child( - Components.label(Text.literal("ℹ")) - .color(Color.ofRgb(0x64B5F6)) - .sizing(Sizing.content(), Sizing.content()) - ); - headerRow.child( - Components.label(Text.literal("Creating New Preset")) - .color(Color.ofRgb(0x64B5F6)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - - this.helpContainer.child(headerRow); - this.helpContainer.child( - Components.label(Text.literal("• Enter a username (will be resolved to UUID automatically)")) - .color(Color.ofRgb(0xE3F2FD)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - this.helpContainer.child( - Components.label(Text.literal("• Friendly name will auto-fill if left empty")) - .color(Color.ofRgb(0xE3F2FD)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - this.helpContainer.child( - Components.label(Text.literal("• Use the slider to set the desired scale")) - .color(Color.ofRgb(0xE3F2FD)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - } - - private void buildButtonSection(FlowLayout contentContainer) { - FlowLayout buttonSection = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .surface(Surface.flat(0x22000000)) - .padding(Insets.of(8)); - - FlowLayout buttonRow = Containers.ltrTextFlow(Sizing.fill(100), Sizing.content()).gap(6); - - this.saveButton = (ButtonComponent) Components.button(Text.literal("Save"), button -> saveCurrentPreset()) - .horizontalSizing(Sizing.fixed(120)); - buttonRow.child(this.saveButton); - - this.cancelButton = (ButtonComponent) Components.button(Text.literal("Cancel"), button -> cancelEditing()) - .horizontalSizing(Sizing.fixed(80)); - buttonRow.child(this.cancelButton); - - this.enableToggleButton = (ButtonComponent) Components.button(Text.literal("Enable"), button -> togglePresetEnabled()) - .horizontalSizing(Sizing.fixed(80)); - buttonRow.child(this.enableToggleButton); - - this.deleteButton = (ButtonComponent) Components.button(Text.literal("Delete"), button -> deleteCurrentPreset()) - .horizontalSizing(Sizing.fixed(80)); - buttonRow.child(this.deleteButton); - - buttonSection.child(buttonRow); - contentContainer.child(buttonSection); - } - - private FlowLayout createFieldSection(String labelText, String placeholder, String helpText, Consumer fieldConsumer) { - FlowLayout section = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .surface(Surface.flat(0x22000000)) - .padding(Insets.of(8)); - - FlowLayout labelRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(4) - .verticalAlignment(VerticalAlignment.CENTER); - - labelRow.child( - Components.label(Text.literal(labelText)) - .color(labelText.contains("*") ? Color.ofRgb(0xFFAB91) : Color.WHITE) - .shadow(false) - ); - section.child(labelRow); - - TextBoxComponent textField = Components.textBox(Sizing.fill(100)); - if (placeholder != null && !placeholder.isEmpty()) { - textField.setSuggestion(placeholder); - textField.onChanged().subscribe(text -> textField.setSuggestion(text.isEmpty() ? placeholder : "")); - } - - fieldConsumer.accept(textField); - section.child(textField); - - if (helpText != null && !helpText.isEmpty()) { - section.child( - Components.label(Text.literal(helpText)) - .color(Color.ofRgb(0x888888)) - .shadow(false) - .sizing(Sizing.fill(100), Sizing.content()) - ); - } - return section; - } - - // --- State Management --- - - public void editPreset(PlayerPreset preset) { - if (preset == null) { - showEmptyState(); - return; - } - this.currentPreset = preset.copy(); // Use a copy for editing - this.hasUnsavedChanges = false; - this.isNewPreset = false; - this.originalIdentifier = preset.identifier; - - if (this.identifierField != null) { - this.identifierField.active = false; - this.identifierField.setEditable(false); - } - - populateFields(this.currentPreset); - updateButtonStates(); - updateTitle(); - updateStatusLabel(); - showEditorState(); - } - - public void createNewPreset() { - PlayerPreset newPreset = new PlayerPreset("", "", 1.0f); - newPreset.enabled = true; - this.isNewPreset = true; - this.currentPreset = newPreset; - this.hasUnsavedChanges = false; - this.originalIdentifier = null; - - if (this.identifierField != null) { - this.identifierField.active = true; - this.identifierField.setEditable(true); - this.identifierField.setEditableColor(0xFFFFFFFF); // Reset to normal color - } - - populateFields(newPreset); - updateButtonStates(); - updateTitle(); - updateStatusLabel(); - showEditorState(); - } - - private void cancelEditing() { - this.currentPreset = null; - this.isNewPreset = false; - this.hasUnsavedChanges = false; - this.originalIdentifier = null; - showEmptyState(); - } - - private void updateTitle() { - if (this.currentPreset == null) { - this.titleLabel.text(Text.literal("Select a Preset")); - } else if (this.isNewPreset) { - this.titleLabel.text(Text.literal("Creating New Preset").formatted(Formatting.GREEN)); - } else { - this.titleLabel.text(Text.literal("Editing: " + this.currentPreset.getEffectiveDisplayName())); - } - } - - private void updateStatusLabel() { - if (this.currentPreset == null) { - this.statusLabel.text(Text.empty()); - } else if (this.isNewPreset) { - this.statusLabel.text(Text.literal("Fill in the required fields to create a new preset").formatted(Formatting.YELLOW)); - } else { - String status = this.currentPreset.enabled ? "Active" : "Disabled"; - this.statusLabel.text(Text.literal("Status: " + status)); - } - } - - private void populateFields(PlayerPreset preset) { - this.identifierField.text(preset.identifier != null ? preset.identifier : ""); - this.friendlyNameField.text(preset.friendlyName != null ? preset.friendlyName : ""); - - // Set slider value - double sliderValue = mapScaleToSlider(preset.scale); - this.scaleSlider.value(sliderValue); - updateScaleValueLabel(preset.scale); - - validateIdentifierFormat(preset.identifier); - } - - private void showEmptyState() { - if (!this.isShowingEmptyState) { - this.removeChild(this.editorContainer); - this.child(this.emptyStateContainer); - this.isShowingEmptyState = true; - } - updateTitle(); - updateStatusLabel(); - } - - private void showEditorState() { - if (this.isShowingEmptyState) { - this.removeChild(this.emptyStateContainer); - this.child(this.editorContainer); - this.isShowingEmptyState = false; - } - - FlowLayout mainContent = this.editorContainer.child(); - if (this.isNewPreset && !mainContent.children().contains(this.helpContainer)) { - mainContent.child(0, this.helpContainer); - } else if (!this.isNewPreset && mainContent.children().contains(this.helpContainer)) { - mainContent.removeChild(this.helpContainer); - } - - // Activate fields and set proper colors - if (this.identifierField != null) { - this.identifierField.active = true; - if (this.isNewPreset) { - this.identifierField.setEditableColor(0xFFFFFFFF); // Normal white color - } - } - if (this.friendlyNameField != null) this.friendlyNameField.active = true; - // Remove the slider.active line - SlimSliderComponent doesn't have this property - - if (this.isNewPreset) { - this.identifierInfoLabel.text(Text.literal("Enter a username (player must be online for UUID resolution).").formatted(Formatting.YELLOW)); - } else { - this.identifierInfoLabel.text(Text.literal("Identifier is read-only when editing a preset.").formatted(Formatting.GRAY)); - } - } - - // --- Validation and Helpers --- - - private void validateField(TextBoxComponent field, boolean isValid, int validColor, int invalidColor) { - if (field != null) { - field.setEditableColor(isValid ? validColor : invalidColor); - } - } - - private void validateIdentifierFormat(String identifier) { - if (identifier == null || identifier.trim().isEmpty()) { - this.isIdentifierValid = false; - if (this.isNewPreset) { - validateField(this.identifierField, false, 0xFFFFFFFF, 0xFFEB1D36); - this.statusLabel.text(Text.literal("Player identifier is required").formatted(Formatting.RED)); - } - return; - } - - String trimmed = identifier.trim(); - - // Check for valid UUID format - boolean isUUIDFormat = isValidUUID(trimmed); - - // Check for valid username format (3-16 chars, alphanumeric + underscore) - boolean isUsernameFormat = trimmed.matches(ScaleConstants.USERNAME_REGEX); - - if (this.isNewPreset) { - this.isIdentifierValid = isUsernameFormat; - - String message; - Formatting color; - - if (isUsernameFormat) { - message = "Username format valid - will resolve when saved"; - color = Formatting.YELLOW; - } else if (trimmed.length() < 3) { - message = "Username too short (minimum 3 characters)"; - color = Formatting.RED; - } else if (trimmed.length() > 16) { - message = "Username too long (maximum 16 characters)"; - color = Formatting.RED; - } else { - message = "Invalid characters - use only letters, numbers, and underscores"; - color = Formatting.RED; - } - - this.statusLabel.text(Text.literal(message).formatted(color)); - validateField(this.identifierField, this.isIdentifierValid, 0xFFFFFFFF, 0xFFEB1D36); - } else { - this.isIdentifierValid = isUUIDFormat || isUsernameFormat; - this.statusLabel.text(Text.literal(isUUIDFormat ? "Valid UUID format" : "Username format") - .formatted(Formatting.GREEN)); - } - } - - private boolean isValidUUID(String input) { - try { - UUID.fromString(input); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - private void updateScaleValueLabel(float scale) { - String scaleText = String.format("%.2f", scale); - Color scaleColor; - - if (scale < 0.8f) { - scaleColor = Color.ofRgb(0x81D4FA); // Light blue for small - } else if (scale > 1.5f) { - scaleColor = Color.ofRgb(0xFFAB91); // Light red for large - } else { - scaleColor = Color.ofRgb(0xA5D6A7); // Light green for normal - } - - this.scaleValueLabel.text(Text.literal(scaleText)).color(scaleColor); - } - - // --- Event Handlers --- - - private void onIdentifierChanged(String newValue) { - if (!this.isNewPreset) return; - - // Sanitize input: remove spaces and invalid characters - String sanitized = newValue.replaceAll("[^a-zA-Z0-9_]", ""); - if (!sanitized.equals(newValue)) { - this.identifierField.text(sanitized); - return; // Will trigger this method again with sanitized value - } - - validateIdentifierFormat(sanitized); - this.hasUnsavedChanges = true; - updateButtonStates(); - } - - private void onFriendlyNameChanged(String newValue) { - if (this.currentPreset == null) return; - if (!newValue.equals(this.currentPreset.friendlyName)) { - this.hasUnsavedChanges = true; - this.currentPreset.friendlyName = newValue.isEmpty() ? null : newValue; - } - updateButtonStates(); - } - - private void onScaleSliderChanged(double sliderValue) { - if (this.currentPreset == null) return; - - float newScale = mapSliderToScale(sliderValue); - if (Math.abs(newScale - this.currentPreset.scale) > 0.001f) { - this.hasUnsavedChanges = true; - this.currentPreset.scale = newScale; - updateScaleValueLabel(newScale); // This was missing! - } - updateButtonStates(); - } - - // --- Button Actions --- - - private void updateButtonStates() { - boolean hasValidPreset = this.currentPreset != null; - boolean canSave = hasValidPreset && this.isIdentifierValid && - (!this.identifierField.getText().trim().isEmpty()); - - this.saveButton.active = canSave; - this.cancelButton.active = true; - this.enableToggleButton.active = !this.isNewPreset && hasValidPreset; - this.deleteButton.active = !this.isNewPreset && hasValidPreset; - - if (!this.isNewPreset && hasValidPreset) { - this.enableToggleButton.setMessage(Text.literal(this.currentPreset.enabled ? "Disable" : "Enable")); - } - - if (this.isNewPreset) { - this.saveButton.setMessage(Text.literal("Create Preset")); - } else if (this.hasUnsavedChanges) { - this.saveButton.setMessage(Text.literal("Save Changes")); - } else { - this.saveButton.setMessage(Text.literal("Save")); - } - } - - private void saveCurrentPreset() { - if (this.currentPreset == null) return; - - // Validate the preset before saving - try { - this.currentPreset.validateForSave(); - } catch (IllegalArgumentException e) { - showErrorMessage("Cannot save: " + e.getMessage()); - return; - } - - if (!this.isIdentifierValid) { - showErrorMessage("Please provide a valid player identifier"); - return; - } - - if (this.isNewPreset) { - String inputIdentifier = this.identifierField.getText().trim(); - - // Additional validation for username format - if (!inputIdentifier.matches(ScaleConstants.USERNAME_REGEX)) { - showErrorMessage("Invalid username format. Use 3-16 alphanumeric characters or underscores."); - return; - } - - // Resolve UUID from username - UUID resolvedUUID = PlayerUUIDResolver.resolvePlayerUUID(inputIdentifier); - if (resolvedUUID == null) { - showErrorMessage("Failed to resolve player: " + inputIdentifier + ". Player must be online or check the username."); - return; - } - - // Set resolved UUID and auto-fill friendly name if empty - this.currentPreset.identifier = resolvedUUID.toString(); - if (this.currentPreset.friendlyName == null || this.currentPreset.friendlyName.trim().isEmpty()) { - this.currentPreset.friendlyName = inputIdentifier; - this.friendlyNameField.text(inputIdentifier); - } - } - - // Final validation before saving - if (!this.currentPreset.isValidForSave()) { - showErrorMessage("Preset contains invalid data and cannot be saved"); - return; - } - - // Save the preset - PlayerPresetManager.addOrUpdatePreset(this.currentPreset); - - // Switch to edit mode after successful creation - if (this.isNewPreset) { - this.isNewPreset = false; - this.identifierField.active = false; - this.identifierField.setEditable(false); - this.identifierField.text(this.currentPreset.identifier); - } - - this.hasUnsavedChanges = false; - this.originalIdentifier = this.currentPreset.identifier; - - updateButtonStates(); - updateTitle(); - updateStatusLabel(); - showEditorState(); - - if (this.onPresetUpdated != null) { - this.onPresetUpdated.accept(this.currentPreset); - } - - showSuccessMessage("Preset saved successfully!"); - } - - private void togglePresetEnabled() { - if (this.currentPreset == null || this.isNewPreset) return; - - this.currentPreset.enabled = !this.currentPreset.enabled; - PlayerPresetManager.setPresetEnabled(this.currentPreset.identifier, this.currentPreset.enabled); - - updateButtonStates(); - updateStatusLabel(); - - if (this.onPresetUpdated != null) { - this.onPresetUpdated.accept(this.currentPreset); - } - - showSuccessMessage("Preset " + (this.currentPreset.enabled ? "enabled" : "disabled") + "!"); - } - - private void deleteCurrentPreset() { - if (this.currentPreset == null || this.isNewPreset) return; - - String presetName = this.currentPreset.getEffectiveDisplayName(); - showConfirmationDialog( - "Delete Preset", - "Are you sure you want to delete the preset '" + presetName + "'?\nThis action cannot be undone.", - () -> { - PlayerPresetManager.removePreset(this.currentPreset.identifier); - if (this.onPresetDeleted != null) { - this.onPresetDeleted.accept(this.currentPreset); - } - this.currentPreset = null; - showEmptyState(); - showSuccessMessage("Preset deleted!"); - }, - null - ); - } - - // --- Dialogs and Messages --- - - private void showConfirmationDialog(String title, String message, Runnable onConfirm, Runnable onCancel) { - FlowLayout overlay = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.fill(100)) - .surface(Surface.flat(0x88000000)) - .horizontalAlignment(HorizontalAlignment.CENTER) - .verticalAlignment(VerticalAlignment.CENTER); - - FlowLayout dialog = (FlowLayout) Containers.verticalFlow(Sizing.fixed(320), Sizing.content()) - .surface(Surface.flat(0xFF424242).and(Surface.outline(0xFF666666))) - .padding(Insets.of(16)) - .horizontalAlignment(HorizontalAlignment.CENTER); - - dialog.child( - Components.label(Text.literal(title)) - .color(Color.WHITE) - .shadow(true) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - .margins(Insets.bottom(4)) - ); - - String[] messageLines = message.split("\n"); - FlowLayout messageContainer = (FlowLayout) Containers.verticalFlow(Sizing.fill(100), Sizing.content()) - .gap(2) - .horizontalAlignment(HorizontalAlignment.CENTER); - - for (String line : messageLines) { - messageContainer.child( - Components.label(Text.literal(line)) - .color(Color.ofRgb(0xCCCCCC)) - .horizontalTextAlignment(HorizontalAlignment.CENTER) - ); - } - dialog.child(messageContainer); - - FlowLayout buttonRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(8) - .horizontalAlignment(HorizontalAlignment.CENTER) - .margins(Insets.top(8)); - - buttonRow.child(Components.button(Text.literal("Cancel"), button -> { - this.removeChild(overlay); - if (onCancel != null) onCancel.run(); - }).horizontalSizing(Sizing.fixed(80))); - - buttonRow.child(Components.button(Text.literal("Confirm"), button -> { - this.removeChild(overlay); - if (onConfirm != null) onConfirm.run(); - }).horizontalSizing(Sizing.fixed(80))); - - dialog.child(buttonRow); - overlay.child(dialog); - this.child(overlay); - } - - private void showErrorMessage(String message) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.player.sendMessage(Text.literal("§c" + message), false); - } - this.statusLabel.text(Text.literal(message).formatted(Formatting.RED)); - } - - private void showSuccessMessage(String message) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.player.sendMessage(Text.literal("§a" + message), false); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetListEntryComponent.java b/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetListEntryComponent.java deleted file mode 100644 index ac0cddb..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/gui/components/PresetListEntryComponent.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.gui.components; - -import com.github.kd_gaming1.scaleme.client.data.PlayerPreset; -import com.mojang.authlib.GameProfile; -import io.wispforest.owo.ui.component.Components; -import io.wispforest.owo.ui.component.TextureComponent; -import io.wispforest.owo.ui.container.Containers; -import io.wispforest.owo.ui.container.FlowLayout; -import io.wispforest.owo.ui.core.*; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.PlayerListEntry; -import net.minecraft.client.util.DefaultSkinHelper; -//? if >=1.21.9 { -/*import net.minecraft.entity.player.SkinTextures; -*///?} else { -import net.minecraft.client.util.SkinTextures; - //?} -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; - -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class PresetListEntryComponent extends FlowLayout { - private static final int HEAD_SIZE = 24; - private static final ConcurrentHashMap skinCache = new ConcurrentHashMap<>(); - - private final PlayerPreset preset; - private boolean isHovered = false; - - public PresetListEntryComponent(PlayerPreset preset) { - super(Sizing.fill(100), Sizing.fixed(36), Algorithm.HORIZONTAL); - - this.preset = preset; - - // Set up the base styling - this.surface(getSurfaceForState(preset.enabled, false)) - .padding(Insets.of(6)) - .verticalAlignment(VerticalAlignment.CENTER) - .margins(Insets.vertical(2)); - - // Add hover effects - this.mouseEnter().subscribe(() -> { - this.isHovered = true; - this.surface(getSurfaceForState(preset.enabled, true)); - }); - - this.mouseLeave().subscribe(() -> { - this.isHovered = false; - this.surface(getSurfaceForState(preset.enabled, false)); - }); - - buildLayout(); - } - - private void buildLayout() { - // Player head section - FlowLayout headSection = (FlowLayout) Containers.horizontalFlow(Sizing.content(), Sizing.content()) - .gap(0) - .verticalAlignment(VerticalAlignment.CENTER); - - // Add player head with subtle border - FlowLayout headContainer = (FlowLayout) Containers.verticalFlow(Sizing.fixed(HEAD_SIZE + 4), Sizing.fixed(HEAD_SIZE + 4)) - .surface(Surface.flat(0x44000000)) - .padding(Insets.of(2)) - .horizontalAlignment(HorizontalAlignment.CENTER) - .verticalAlignment(VerticalAlignment.CENTER); - - headContainer.child(createPlayerHead(preset)); - headSection.child(headContainer); - - this.child(headSection); - - // Main content section - FlowLayout contentSection = (FlowLayout) Containers.verticalFlow(Sizing.expand(), Sizing.content()) - .gap(2) - .verticalAlignment(VerticalAlignment.CENTER); - - // Name row - FlowLayout nameRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(6) - .verticalAlignment(VerticalAlignment.CENTER); - - nameRow.child( - Components.label(Text.literal(preset.getEffectiveDisplayName())) - .color(getNameColor(preset.enabled)) - .shadow(false) - .sizing(Sizing.expand(), Sizing.content()) - ); - - // Status indicator - nameRow.child( - Components.label(Text.literal(preset.enabled ? "●" : "○")) - .color(preset.enabled ? Color.ofRgb(0x4CAF50) : Color.ofRgb(0x757575)) - .sizing(Sizing.content(), Sizing.content()) - ); - - contentSection.child(nameRow); - - // Scale info row - FlowLayout scaleRow = (FlowLayout) Containers.horizontalFlow(Sizing.fill(100), Sizing.content()) - .gap(6) - .verticalAlignment(VerticalAlignment.CENTER); - - scaleRow.child( - Components.label(Text.literal("Scale: " + String.format("%.2f", preset.scale))) - .color(getScaleColor(preset)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - - // Add a subtle scale indicator bar - if (preset.enabled) { - scaleRow.child(createScaleBar(preset.scale)); - } - - contentSection.child(scaleRow); - this.child(contentSection); - - // Action section (right side) - FlowLayout actionSection = (FlowLayout) Containers.verticalFlow(Sizing.content(), Sizing.content()) - .gap(2) - .verticalAlignment(VerticalAlignment.CENTER) - .horizontalAlignment(HorizontalAlignment.CENTER); - - // Status text - actionSection.child( - Components.label(Text.literal(preset.enabled ? "ACTIVE" : "DISABLED")) - .color(preset.enabled ? Color.ofRgb(0x4CAF50) : Color.ofRgb(0xFF9800)) - .shadow(false) - .sizing(Sizing.content(), Sizing.content()) - ); - - this.child(actionSection); - } - - private FlowLayout createScaleBar(float scale) { - int barWidth = 40; - int barHeight = 3; - - // Map scale 0.1 (empty) to 3.0 (full) - float normalizedScale = Math.max(0, Math.min(1, (scale - 0.1f) / (3.0f - 0.1f))); - int fillWidth = (int)(barWidth * normalizedScale); - - FlowLayout scaleBar = (FlowLayout) Containers.horizontalFlow(Sizing.fixed(barWidth), Sizing.fixed(barHeight)) - .surface(Surface.flat(0x33FFFFFF)); - - if (fillWidth > 0) { - scaleBar.child( - Components.box(Sizing.fixed(fillWidth), Sizing.fixed(barHeight)) - .color(getScaleBarColor(scale)) - ); - } - - return scaleBar; - } - - private Color getScaleBarColor(float scale) { - if (scale < 0.8f) return Color.ofRgb(0x2196F3); // Blue for small - if (scale > 1.5f) return Color.ofRgb(0xFF5722); // Red for large - return Color.ofRgb(0x4CAF50); // Green for normal - } - - private Surface getSurfaceForState(boolean enabled, boolean hovered) { - if (hovered) { - return enabled ? - Surface.flat(0x44424242).and(Surface.outline(0xFF4CAF50)) : - Surface.flat(0x44424242).and(Surface.outline(0xFF757575)); - } else { - return enabled ? - Surface.flat(0x33424242) : - Surface.flat(0x22424242); - } - } - - private Color getNameColor(boolean enabled) { - return enabled ? Color.WHITE : Color.ofRgb(0xBBBBBB); - } - - private Color getScaleColor(PlayerPreset preset) { - if (!preset.enabled) return Color.ofRgb(0x888888); - - if (preset.scale < 0.8f) return Color.ofRgb(0x81D4FA); // Light blue - if (preset.scale > 1.5f) return Color.ofRgb(0xFFAB91); // Light red - return Color.ofRgb(0xA5D6A7); // Light green - } - - private TextureComponent createPlayerHead(PlayerPreset preset) { - SkinTextures skinTextures = getSkinTextures(preset); - //? if >=1.21.9 { - /*Identifier skinTexture = skinTextures.body().id(); - *///?} else { - Identifier skinTexture = skinTextures.texture(); - //?} - - return (TextureComponent) Components.texture(skinTexture, 8, 8, 8, 8, 64, 64) - .sizing(Sizing.fixed(HEAD_SIZE), Sizing.fixed(HEAD_SIZE)); - } - - private SkinTextures getSkinTextures(PlayerPreset preset) { - try { - if (preset.isUUID()) { - UUID playerUUID = UUID.fromString(preset.identifier); - - // Check cache first - SkinTextures cached = skinCache.get(playerUUID); - if (cached != null) { - return cached; - } - - // Try to get from player list (for online players) - MinecraftClient client = MinecraftClient.getInstance(); - if (client.getNetworkHandler() != null) { - PlayerListEntry playerListEntry = client.getNetworkHandler().getPlayerListEntry(playerUUID); - if (playerListEntry != null) { - SkinTextures textures = playerListEntry.getSkinTextures(); - skinCache.put(playerUUID, textures); - return textures; - } - } - - // Fallback: Use default skin and try to fetch async - SkinTextures defaultTextures = DefaultSkinHelper.getSkinTextures(playerUUID); - skinCache.put(playerUUID, defaultTextures); - - // Try to fetch the actual skin asynchronously - fetchSkinAsync(playerUUID, preset.getEffectiveDisplayName()); - - return defaultTextures; - } - } catch (IllegalArgumentException e) { - // Invalid UUID, fall through to default - } - - // Default steve/alex skin for non-UUID presets or invalid UUIDs - return DefaultSkinHelper.getSkinTextures(UUID.randomUUID()); - } - - private void fetchSkinAsync(UUID playerUUID, String playerName) { - Util.getMainWorkerExecutor().execute(() -> { - try { - MinecraftClient client = MinecraftClient.getInstance(); - GameProfile profile = new GameProfile(playerUUID, playerName); - - //? if >=1.21.9 { - /*// Fetch skin textures directly using skin provider - client.getSkinProvider().fetchSkinTextures(profile).thenAccept(optionalTextures -> { - optionalTextures.ifPresent(textures -> { - skinCache.put(playerUUID, textures); - }); - }); - *///?} else { - // Fetch complete profile - var completeProfile = client.getSessionService().fetchProfile(playerUUID, false).profile(); - if (completeProfile != null) { - client.getSkinProvider().fetchSkinTextures(completeProfile).thenAccept(optionalTextures -> { - optionalTextures.ifPresent(textures -> { - skinCache.put(playerUUID, textures); - }); - }); - } - //?} - } catch (Exception e) { - // Ignore errors in async skin fetching - } - }); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/HeldItemRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/HeldItemRendererMixin.java deleted file mode 100644 index f1830e4..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/HeldItemRendererMixin.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.ScaleConstants; -import com.github.kd_gaming1.scaleme.client.util.ScaleTransformer; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import com.llamalad7.mixinextras.sugar.Local; -import net.minecraft.client.render.item.HeldItemRenderer; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.item.ItemStack; -import net.minecraft.util.Hand; -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.ModifyArgs; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.invoke.arg.Args; - -/** - * Mixin to handle custom scaling and positioning of held items in first person. - * Applies transformations before the item is rendered. - */ -@Mixin(HeldItemRenderer.class) -public class HeldItemRendererMixin { - - /** - * Applies custom scale, rotation, and position transformations to held items. - *

- * Injection point is right before the item rendering call, allowing us to - * modify the matrix stack before the item is drawn. - */ - @Inject( - method = "renderFirstPersonItem", - //? if >=1.21.9 { - /*at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/command/OrderedRenderCommandQueue;I)V" - ) - *///?} else { - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V" - ) - //?} - ) - private void applyItemTransformations( - CallbackInfo ci, - @Local(argsOnly = true) Hand hand, - @Local(argsOnly = true) MatrixStack matrices, - @Local ItemStack item) { - - // Early exit if feature disabled or no item - if (!shouldApplyTransformations(item)) { - return; - } - - // Build and apply transformation configuration - ScaleTransformer.ItemTransformConfig config = - ScaleTransformer.ItemTransformConfig.fromConfig(); - - if (config.hasTransformations()) { - ScaleTransformer.applyItemTransform(matrices, config); - } - } - - /** - * Modifies the attack cooldown progress to disable swing animation bobbing. - *

- * When bobbing is disabled, returns 1.0f (full cooldown) instead of the actual - * progress, which prevents the visual bobbing effect. - */ - //? if >=1.21.11 { - /*@ModifyExpressionValue( - method = "updateHeldItems", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/network/ClientPlayerEntity;getHandEquippingProgress(F)F" - ) - ) - - private float modifySwingBobbing(float originalCooldown) { - if (shouldDisableSwingBobbing()) { - return ScaleConstants.SWING_BOBBING_DISABLED_VALUE; - } - return originalCooldown; - } - *///?} else { - @ModifyExpressionValue( - method = "updateHeldItems", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/network/ClientPlayerEntity;getAttackCooldownProgress(F)F" - ) - ) - - private float modifySwingBobbing(float originalCooldown) { - if (shouldDisableSwingBobbing()) { - return ScaleConstants.SWING_BOBBING_DISABLED_VALUE; - } - return originalCooldown; - } - //?} - - // ===== Helper Methods ===== - - /** - * Determines if item transformations should be applied. - * @param item The item stack being rendered - * @return true if transformations should apply - */ - private boolean shouldApplyTransformations(ItemStack item) { - return ScaleMeConfig.enableItemScaleAndPosition && - item != null && - !item.isEmpty(); - } - - /** - * Determines if swing animation bobbing should be disabled. - * @return true if bobbing should be disabled - */ - private boolean shouldDisableSwingBobbing() { - return (ScaleMeConfig.enableItemScaleAndPosition || - ScaleMeConfig.enableItemSwingModifications) && - ScaleMeConfig.disableSwingAnimationBobbing; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityMixin.java deleted file mode 100644 index 6c9bd9a..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityMixin.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.ScaleConstants; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.LivingEntity; -import net.minecraft.world.World; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; - -/** - * Mixin to modify hand swing animations and durations for living entities. - *

- * Provides control over: - *

    - *
  • Animation speed/duration
  • - *
  • Mining effect influence
  • - *
  • Haste/Mining Fatigue effects
  • - *
- */ -@Mixin(LivingEntity.class) -public abstract class LivingEntityMixin extends Entity { - - protected LivingEntityMixin(EntityType type, World world) { - super(type, world); - } - - /** - * Modifies mining effect checks (Haste/Mining Fatigue) when configured to ignore them. - *

- * This injection captures two boolean checks: - * 1. StatusEffectUtil.hasHaste() - * 2. hasStatusEffect() for Mining Fatigue - *

- * When {@code ignoreMiningEffects} is enabled, these checks are forced to false, - * preventing mining effects from influencing animation speed. - */ - @ModifyExpressionValue( - method = "getHandSwingDuration", - at = { - @At(value = "INVOKE", target = "Lnet/minecraft/entity/effect/StatusEffectUtil;hasHaste(Lnet/minecraft/entity/LivingEntity;)Z"), - @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;hasStatusEffect(Lnet/minecraft/registry/entry/RegistryEntry;)Z") - }, - require = 2 - ) - private boolean modifyMiningEffectCheck(boolean originalHasEffect) { - if (!scaleme$shouldModifySwing()) { - return originalHasEffect; - } - - // If we should ignore mining effects, return false regardless of actual effect status - return !ScaleMeConfig.ignoreMiningEffects && originalHasEffect; - } - - /** - * Modifies the base swing duration constant to apply custom animation speed. - */ - //? if >=1.21.11 { - /*@ModifyVariable( - method = "getHandSwingDuration", - at = @At("STORE"), - ordinal = 0 - ) - private int modifySwingDurationStoredBase(int vanillaDuration) { - if (!scaleme$shouldModifySwing()) { - return vanillaDuration; - } - - if (ScaleMeConfig.disableItemAnimation) { - return Integer.MAX_VALUE; - } - - return scaleme$calculateModifiedDuration(vanillaDuration, ScaleMeConfig.itemAnimationSpeed); - } - *///?} else { - @ModifyExpressionValue( - method = "getHandSwingDuration", - at = @At(value = "CONSTANT", args = "intValue=6") - ) - private int modifySwingDuration(int vanillaDuration) { - if (!scaleme$shouldModifySwing()) { - return vanillaDuration; - } - - if (ScaleMeConfig.disableItemAnimation) { - return Integer.MAX_VALUE; - } - - return scaleme$calculateModifiedDuration(vanillaDuration, ScaleMeConfig.itemAnimationSpeed); - } - //?} - - // ===== Helper Methods ===== - - /** - * Determines if swing modifications should be applied to this entity. - *

- * Modifications only apply to: - *

    - *
  • The main client player
  • - *
  • When the feature is enabled
  • - *
  • When a client player exists
  • - *
- * - * @return true if modifications should apply - */ - @Unique - private boolean scaleme$shouldModifySwing() { - if (!ScaleMeConfig.enableItemSwingModifications) { - return false; - } - - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player == null) { - return false; - } - - // Only apply to the main player - return (Object) this instanceof AbstractClientPlayerEntity player && - player.isMainPlayer(); - } - - /** - * Calculates the modified swing duration based on animation speed. - *

- * Formula: modified = original / speed - * Result is clamped to prevent extreme values. - * - * @param originalDuration The vanilla duration - * @param animationSpeed The configured speed multiplier - * @return The modified and clamped duration - */ - @Unique - private int scaleme$calculateModifiedDuration(int originalDuration, float animationSpeed) { - // Prevent division by zero or negative speeds - if (animationSpeed <= 0.0f) { - return originalDuration; - } - - int modified = Math.round(originalDuration / animationSpeed); - - // Clamp to safe range - return Math.max( - ScaleConstants.MIN_SWING_DURATION, - Math.min(ScaleConstants.MAX_SWING_DURATION, modified) - ); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityRendererMixin.java deleted file mode 100644 index fa8014f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/LivingEntityRendererMixin.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.ScaleManager; -import com.github.kd_gaming1.scaleme.client.util.ScaleTransformer; -import com.github.kd_gaming1.scaleme.client.util.VillagerEntityRenderStateAccessor; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.state.LivingEntityRenderState; -import net.minecraft.client.render.entity.state.VillagerEntityRenderState; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.passive.VillagerEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * Mixin to handle scaling for living entities, specifically villager NPCs. - *

- * This focuses on Hypixel villager NPCs when the appropriate config is enabled. - */ -@Mixin(LivingEntityRenderer.class) -public class LivingEntityRendererMixin { - - /** - * Applies custom scaling to villager NPCs. - *

- * Injected at HEAD to intercept before vanilla scaling logic. - * Cancels vanilla scaling when custom scaling is applied. - */ - @Inject( - method = "scale(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;)V", - at = @At("HEAD"), - cancellable = true - ) - private void applyVillagerNpcScaling( - LivingEntityRenderState renderState, - MatrixStack matrices, - CallbackInfo ci) { - - if (renderState == null || matrices == null) { - return; - } - - // Only process villager render states - if (!(renderState instanceof VillagerEntityRenderState villagerRenderState)) { - return; - } - - if (scaleme$tryApplyVillagerScaling(villagerRenderState, matrices)) { - ci.cancel(); // Prevent vanilla scaling - } - } - - // ===== Helper Methods ===== - - /** - * Attempts to apply villager NPC scaling if enabled and applicable. - * - * @param villagerRenderState The villager render state - * @param matrices The matrix stack for transformations - * @return true if scaling was applied and vanilla should be cancelled - */ - @Unique - private boolean scaleme$tryApplyVillagerScaling( - VillagerEntityRenderState villagerRenderState, - MatrixStack matrices) { - - if (!ScaleMeConfig.enableVillagerNpcScaling) { - return false; - } - - VillagerEntityRenderStateAccessor accessor = - (VillagerEntityRenderStateAccessor) villagerRenderState; - VillagerEntity villager = accessor.scaleme$getVillagerEntity(); - - if (villager == null) { - return false; - } - - // Apply villager NPC scale from scale manager - float scale = ScaleManager.getVillagerNpcScale(); - ScaleTransformer.applyScale(matrices, scale); - - return true; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/MixinInGameHud.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/MixinInGameHud.java deleted file mode 100644 index e84e015..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/MixinInGameHud.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.hud.InGameHud; -import net.minecraft.client.option.Perspective; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; - -@Mixin(InGameHud.class) -public class MixinInGameHud { - - @ModifyExpressionValue( - method = "renderCrosshair(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/client/render/RenderTickCounter;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/option/Perspective;isFirstPerson()Z") - ) - private boolean shouldRenderCrosshair(boolean original) { - if (original) { - return true; - } - - MinecraftClient client = MinecraftClient.getInstance(); - Perspective perspective = client.options.getPerspective(); - - if (perspective.isFrontView()) { - return ScaleMeConfig.enableCrosshairInThirdPersonFront; - } else { - return ScaleMeConfig.enableCrosshairInThirdPerson; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PerspectiveMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PerspectiveMixin.java deleted file mode 100644 index f9bdba8..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PerspectiveMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import net.minecraft.client.option.Perspective; -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.CallbackInfoReturnable; - -/** - * Mixin to modify camera perspective cycling behavior. - *

- * Allows skipping the front-facing (selfie) camera view when configured. - */ -@Mixin(Perspective.class) -public class PerspectiveMixin { - - /** - * Intercepts perspective cycling to skip front view when selfie cam is disabled. - *

- * Normal cycle: FIRST_PERSON → THIRD_PERSON_BACK → THIRD_PERSON_FRONT → FIRST_PERSON - * Modified cycle: FIRST_PERSON → THIRD_PERSON_BACK → FIRST_PERSON - *

- * This prevents players from accidentally entering front-facing view when they - * have it disabled. - * - * @param cir Callback info returnable for the next perspective - */ - @Inject( - method = "next", - at = @At("HEAD"), - cancellable = true - ) - private void skipFrontViewIfDisabled(CallbackInfoReturnable cir) { - if (!ScaleMeConfig.disableSelfieCam) { - return; // Allow normal cycling - } - - Perspective current = (Perspective) (Object) this; - - // When in third-person back view and cycling forward, - // skip front view and go directly to first person - if (current == Perspective.THIRD_PERSON_BACK) { - cir.setReturnValue(Perspective.FIRST_PERSON); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRenderStateMixin.java deleted file mode 100644 index d2deb3f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRenderStateMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.PlayerEntityRenderStateAccessor; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.entity.state.PlayerEntityRenderState; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; - -import java.util.UUID; - -@Mixin(PlayerEntityRenderState.class) -public class PlayerEntityRenderStateMixin implements PlayerEntityRenderStateAccessor { - - @Unique - private UUID scaleme$playerUUID; - @Unique - private AbstractClientPlayerEntity scaleme$playerEntity; - - @Override - public void scaleme$setPlayerUUID(UUID uuid) { - this.scaleme$playerUUID = uuid; - } - - @Override - public UUID scaleme$getPlayerUUID() { - return this.scaleme$playerUUID; - } - - @Override - public void scaleme$setPlayerEntity(AbstractClientPlayerEntity player) { - this.scaleme$playerEntity = player; - } - - @Override - public AbstractClientPlayerEntity scaleme$getPlayerEntity() { - return this.scaleme$playerEntity; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRendererMixin.java deleted file mode 100644 index 3feccd5..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/PlayerEntityRendererMixin.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.HypixelNpcUtil; -import com.github.kd_gaming1.scaleme.client.util.PlayerEntityRenderStateAccessor; -import com.github.kd_gaming1.scaleme.client.util.ScaleManager; -import com.github.kd_gaming1.scaleme.client.util.ScaleTransformer; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.client.render.entity.state.PlayerEntityRenderState; -import net.minecraft.client.util.math.MatrixStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.UUID; - -/** - * Mixin to handle player model scaling in the rendering pipeline. - *

- * This mixin operates in two phases: - *

    - *
  1. Store player data in render state during update
  2. - *
  3. Apply scaling transformations during rendering
  4. - *
- */ -@Mixin(PlayerEntityRenderer.class) -public class PlayerEntityRendererMixin { - - /** - * Stores player UUID and entity reference in the render state for later use. - *

- * Injected at TAIL of updateRenderState to ensure all vanilla data is populated first. - */ - @Inject( - //? if >=1.21.9 { - /*method = "updateRenderState(Lnet/minecraft/entity/PlayerLikeEntity;Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;F)V", - at = @At("TAIL") - *///?} else { - method = "updateRenderState(Lnet/minecraft/client/network/AbstractClientPlayerEntity;Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;F)V", - at = @At("TAIL") - //?} - ) - private void storePlayerData( - //? if >=1.21.9 { - /*net.minecraft.entity.PlayerLikeEntity player, - *///?} else { - AbstractClientPlayerEntity player, - //?} - PlayerEntityRenderState renderState, - float tickDelta, - CallbackInfo ci) { - - if (player == null || renderState == null) { - return; - } - - //? if >=1.21.9 { - /*// Cast PlayerLikeEntity to AbstractClientPlayerEntity for 1.21.9+ - if (!(player instanceof AbstractClientPlayerEntity clientPlayer)) { - return; - } - PlayerEntityRenderStateAccessor accessor = (PlayerEntityRenderStateAccessor) renderState; - accessor.scaleme$setPlayerUUID(clientPlayer.getUuid()); - accessor.scaleme$setPlayerEntity(clientPlayer); - *///?} else { - PlayerEntityRenderStateAccessor accessor = (PlayerEntityRenderStateAccessor) renderState; - accessor.scaleme$setPlayerUUID(player.getUuid()); - accessor.scaleme$setPlayerEntity(player); - //?} - } - - - /** - * Applies scale transformations to player models based on configuration. - *

- * Handles three scaling scenarios: - *

    - *
  • Hypixel NPCs (when enabled)
  • - *
  • Regular players (from presets or config)
  • - *
  • Default scaling (no transformation)
  • - *
- *

- * Cancels vanilla scaling when custom scaling is applied. - */ - @Inject( - method = "scale(Lnet/minecraft/client/render/entity/state/PlayerEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;)V", - at = @At("HEAD"), - cancellable = true - ) - private void applyCustomScaling( - PlayerEntityRenderState renderState, - MatrixStack matrices, - CallbackInfo ci) { - - if (renderState == null || matrices == null) { - return; - } - - PlayerEntityRenderStateAccessor accessor = (PlayerEntityRenderStateAccessor) renderState; - AbstractClientPlayerEntity player = accessor.scaleme$getPlayerEntity(); - UUID playerUUID = accessor.scaleme$getPlayerUUID(); - - // Early exit: Skip own player scaling if disabled - if (player != null && playerUUID != null) { - net.minecraft.client.MinecraftClient client = net.minecraft.client.MinecraftClient.getInstance(); - if (client.player != null && playerUUID.equals(client.player.getUuid())) { - if (!ScaleMeConfig.enableOwnPlayerScaling) { - return; // Exit without cancelling - let vanilla scaling happen - } - } - } - - // Check if this is an NPC FIRST - if (player != null && HypixelNpcUtil.isHypixelNpc(player)) { - // This is an NPC - only apply NPC scaling, never player scaling - if (ScaleMeConfig.enableNpcScaling) { - float npcScale = ScaleManager.getNpcScale(); - ScaleTransformer.applyScale(matrices, npcScale); - ci.cancel(); - } - // Return regardless - NPCs should never use player scaling - return; - } - - // Not an NPC - apply regular player scaling - if (scaleme$tryApplyPlayerScaling(playerUUID, matrices)) { - ci.cancel(); - } - } - - // ===== Helper Methods ===== - - /** - * Attempts to apply player-specific scaling from presets or config. - * - * @param playerUUID The player's UUID - * @param matrices The matrix stack for transformations - * @return true if player scaling was applied - */ - @Unique - private boolean scaleme$tryApplyPlayerScaling(UUID playerUUID, MatrixStack matrices) { - if (playerUUID == null) { - return false; - } - - float scale = ScaleManager.getCurrentScale(playerUUID); - ScaleTransformer.applyScale(matrices, scale); - - return true; // Always return true if we have a UUID (even if scale is 1.0) - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRenderStateMixin.java deleted file mode 100644 index 34c4b12..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRenderStateMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.VillagerEntityRenderStateAccessor; -import net.minecraft.client.render.entity.state.VillagerEntityRenderState; -import net.minecraft.entity.passive.VillagerEntity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; - -@Mixin(VillagerEntityRenderState.class) -public class VillagerEntityRenderStateMixin implements VillagerEntityRenderStateAccessor { - - @Unique - private VillagerEntity scaleme$villagerEntity; - - @Override - public void scaleme$setVillagerEntity(VillagerEntity villager) { - this.scaleme$villagerEntity = villager; - } - - @Override - public VillagerEntity scaleme$getVillagerEntity() { - return this.scaleme$villagerEntity; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRendererMixin.java deleted file mode 100644 index af24b6f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/mixin/VillagerEntityRendererMixin.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.mixin; - -import com.github.kd_gaming1.scaleme.client.util.VillagerEntityRenderStateAccessor; -import net.minecraft.client.render.entity.VillagerEntityRenderer; -import net.minecraft.client.render.entity.state.VillagerEntityRenderState; -import net.minecraft.entity.passive.VillagerEntity; -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 to store villager entity reference in render state for scaling. - *

- * This is required because the render state doesn't normally contain - * the entity reference, but we need it to apply custom scaling. - */ -@Mixin(VillagerEntityRenderer.class) -public class VillagerEntityRendererMixin { - - /** - * Stores the villager entity in the render state for later use during rendering. - *

- * Injected at TAIL to ensure all vanilla data is populated first. - * - * @param villager The villager entity being rendered - * @param renderState The render state to populate - * @param tickDelta Partial tick time - * @param ci Callback info - */ - @Inject( - method = "updateRenderState(Lnet/minecraft/entity/passive/VillagerEntity;Lnet/minecraft/client/render/entity/state/VillagerEntityRenderState;F)V", - at = @At("TAIL") - ) - private void storeVillagerEntity( - VillagerEntity villager, - VillagerEntityRenderState renderState, - float tickDelta, - CallbackInfo ci) { - - if (villager == null || renderState == null) { - return; - } - - VillagerEntityRenderStateAccessor accessor = (VillagerEntityRenderStateAccessor) renderState; - accessor.scaleme$setVillagerEntity(villager); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelDetector.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelDetector.java deleted file mode 100644 index 7d6982f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelDetector.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.scoreboard.*; - -import java.util.Collection; -import java.util.regex.Pattern; - -/** - * Utility for detecting Hypixel server contexts and game modes. - *

- * Provides safety checks to prevent scaling in competitive or gameplay-sensitive areas. - */ -public class HypixelDetector { - - // Server brand detection - private static final String HYPIXEL_BRAND = "hypixel"; - - // Unsafe game mode prefixes (where scaling should be disabled) - private static final String[] UNSAFE_MODE_PREFIXES = { - "SKYBLOCK", - "LOBBY", - "LIMBO", - "HOUSING", - "PROTOTYPE", - "HYPIXEL" - }; - - // Dungeon detection marker - private static final String DUNGEON_MARKER_PREFIX = "CLEARED:"; - - // Color code pattern for stripping - private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§."); - - // Non-breaking space character - private static final char NBSP = '\u00A0'; - - /** - * Checks if the player is in a "safe" game mode on Hypixel. - *

- * Safe modes exclude competitive/gameplay areas like: - *

    - *
  • SkyBlock
  • - *
  • Lobby
  • - *
  • Limbo
  • - *
  • Housing
  • - *
  • Prototype
  • - *
  • Hypixel logo screen
  • - *
- * - * @return true if in a safe game mode, false otherwise - */ - public static boolean isSafeGameMode() { - MinecraftClient client = MinecraftClient.getInstance(); - - // Must be connected to a server - if (!isConnectedToServer(client)) { - return false; - } - - // Must be on Hypixel - if (!isHypixelServer(client)) { - return false; - } - - // Check scoreboard for game mode - String gameMode = getCurrentGameMode(client); - if (gameMode == null) { - return false; - } - - // Return true if NOT in any unsafe game mode - return !isUnsafeGameMode(gameMode); - } - - /** - * Checks if NPC scaling should be disabled for gameplay safety. - *

- * NPC scaling is disabled when: - *

    - *
  • Not in a safe game mode (e.g., in SkyBlock, Lobby, Housing)
  • - *
  • AND inside a dungeon
  • - *
- *

- * This prevents NPC scaling from affecting competitive gameplay in dungeons. - * Note: The method name is misleading (kept for compatibility). It returns true - * when scaling should be DISABLED, not when it's safe to scale. - * - * @return true if NPC scaling should be disabled (in dungeons) - */ - public static boolean isSafeForNPCScaling() { - return !isSafeGameMode() && isInDungeon(); - } - - /** - * Detects if the player is currently inside a Hypixel dungeon. - *

- * Detection is done by scanning the scoreboard for a line starting with "CLEARED:". - * - * @return true if in a dungeon, false otherwise - */ - public static boolean isInDungeon() { - MinecraftClient client = MinecraftClient.getInstance(); - - if (client.world == null) { - return false; - } - - Scoreboard scoreboard = client.world.getScoreboard(); - ScoreboardObjective objective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR); - - if (objective == null) { - return false; - } - - Collection entries = scoreboard.getScoreboardEntries(objective); - - for (ScoreboardEntry entry : entries) { - String line = buildScoreboardLine(entry, scoreboard); - String normalized = normalizeScoreboardLine(line); - - if (isDungeonLine(normalized)) { - return true; - } - } - - return false; - } - - // ===== Private Helper Methods ===== - - /** - * Checks if the client is connected to a server. - */ - private static boolean isConnectedToServer(MinecraftClient client) { - return client.getNetworkHandler() != null; - } - - /** - * Checks if the connected server is Hypixel. - */ - private static boolean isHypixelServer(MinecraftClient client) { - String brand = client.getNetworkHandler().getBrand(); - return brand != null && brand.toLowerCase().contains(HYPIXEL_BRAND); - } - - /** - * Gets the current game mode from the scoreboard title. - * - * @return The game mode title (uppercase, color codes stripped), or null if unavailable - */ - private static String getCurrentGameMode(MinecraftClient client) { - if (client.world == null) { - return null; - } - - Scoreboard scoreboard = client.world.getScoreboard(); - ScoreboardObjective objective = scoreboard.getObjectiveForSlot(ScoreboardDisplaySlot.SIDEBAR); - - if (objective == null) { - return null; - } - - String title = objective.getDisplayName().getString(); - return stripColors(title).toUpperCase(); - } - - /** - * Checks if a game mode is considered unsafe for scaling. - */ - private static boolean isUnsafeGameMode(String gameMode) { - for (String unsafePrefix : UNSAFE_MODE_PREFIXES) { - if (gameMode.startsWith(unsafePrefix)) { - return true; - } - } - return false; - } - - /** - * Builds a complete scoreboard line including team prefix/suffix. - */ - private static String buildScoreboardLine(ScoreboardEntry entry, Scoreboard scoreboard) { - String owner = entry.owner(); - Team team = scoreboard.getScoreHolderTeam(owner); - - StringBuilder lineBuilder = new StringBuilder(); - - if (team != null) { - lineBuilder.append(team.getPrefix().getString()); - } - - lineBuilder.append(owner); - - if (team != null) { - lineBuilder.append(team.getSuffix().getString()); - } - - return lineBuilder.toString(); - } - - /** - * Normalizes a scoreboard line for comparison. - *

- * Normalization includes: - *

    - *
  • Strip color codes
  • - *
  • Replace non-breaking spaces
  • - *
  • Collapse whitespace
  • - *
  • Trim and uppercase
  • - *
- */ - private static String normalizeScoreboardLine(String line) { - try { - String noColors = stripColors(line); - return noColors - .replace(NBSP, ' ') - .replaceAll("\\s+", " ") - .trim() - .toUpperCase(); - } catch (Exception e) { - // Fallback to basic normalization if something goes wrong - return line.toUpperCase().trim(); - } - } - - /** - * Checks if a normalized line indicates a dungeon. - */ - private static boolean isDungeonLine(String normalizedLine) { - return normalizedLine.startsWith(DUNGEON_MARKER_PREFIX) || - normalizedLine.contains(DUNGEON_MARKER_PREFIX); - } - - /** - * Strips Minecraft formatting codes (§ followed by any character). - * - * @param text The text to strip - * @return Text with all color codes removed - */ - private static String stripColors(String text) { - if (text == null) { - return ""; - } - return COLOR_CODE_PATTERN.matcher(text).replaceAll(""); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelNpcUtil.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelNpcUtil.java deleted file mode 100644 index 032eb6f..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/HypixelNpcUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.scoreboard.Team; -import net.minecraft.scoreboard.AbstractTeam; - -/** - * Utility for detecting Hypixel NPCs. - *

- * NPCs on Hypixel are identified by their scoreboard team having name tag visibility - * set to NEVER, which is used to hide their name tags from players. - */ -public class HypixelNpcUtil { - - /** - * Checks if the given player entity is a Hypixel NPC. - *

- * Detection method: NPCs on Hypixel have their scoreboard team's - * name tag visibility rule set to {@link AbstractTeam.VisibilityRule#NEVER}. - * - * @param player The player entity to check - * @return true if this entity is a Hypixel NPC, false otherwise - */ - public static boolean isHypixelNpc(AbstractClientPlayerEntity player) { - if (player == null) { - return false; - } - - Team team = player.getScoreboardTeam(); - if (team == null) { - return false; - } - - AbstractTeam.VisibilityRule nameTagRule = team.getNameTagVisibilityRule(); - return nameTagRule == AbstractTeam.VisibilityRule.NEVER; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerEntityRenderStateAccessor.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerEntityRenderStateAccessor.java deleted file mode 100644 index 5c1abd7..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerEntityRenderStateAccessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import net.minecraft.client.network.AbstractClientPlayerEntity; - -import java.util.UUID; - -public interface PlayerEntityRenderStateAccessor { - void scaleme$setPlayerUUID(UUID uuid); - UUID scaleme$getPlayerUUID(); - - void scaleme$setPlayerEntity(AbstractClientPlayerEntity player); - AbstractClientPlayerEntity scaleme$getPlayerEntity(); -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerPresetManager.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerPresetManager.java deleted file mode 100644 index 7f995b5..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerPresetManager.java +++ /dev/null @@ -1,493 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import com.github.kd_gaming1.scaleme.client.data.PlayerPreset; -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Unified player scaling system with O(1) performance. - *

- * This manager handles: - *

    - *
  • Player-specific presets (priority 1)
  • - *
  • Own player config scaling (priority 2)
  • - *
  • Global "other players" scaling (priority 3)
  • - *
- *

- * Provides smooth scale interpolation when enabled in config. - * Thread-safe for concurrent access. - */ -public class PlayerPresetManager { - private static final Logger LOGGER = LoggerFactory.getLogger(PlayerPresetManager.class); - private static final String PRESET_FILE_NAME = "scaleme_player_presets.json"; - - // Smooth scaling configuration - private static final float SMOOTH_INTERPOLATION_SPEED = 0.15f; - private static final float SMOOTH_THRESHOLD = 0.001f; - - // Storage - private static final List presets = new CopyOnWriteArrayList<>(); - private static final ConcurrentHashMap currentScales = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap targetScales = new ConcurrentHashMap<>(); - - // File I/O - private static final File presetFile; - private static final Gson gson; - - static { - File configDir = FabricLoader.getInstance().getConfigDir().toFile(); - presetFile = new File(configDir, PRESET_FILE_NAME); - gson = new GsonBuilder().setPrettyPrinting().create(); - } - - // ===== Initialization ===== - - /** - * Initializes the preset manager by loading presets from disk. - * Should be called once during mod initialization. - */ - public static void init() { - loadPresets(); - refreshAllScales(); - } - - // ===== Scale Retrieval ===== - - /** - * Returns the current scale for a player with O(1) lookup. - *

- * Scale priority: - *

    - *
  1. Player-specific preset (if enabled)
  2. - *
  3. Own player config scale
  4. - *
  5. Global "other players" config scale
  6. - *
- *

- * Returns 1.0 if: - *

    - *
  • playerUUID is null
  • - *
  • In a safe game mode (minigame) - scaling disabled for competitive gameplay
  • - *
- * - * @param playerUUID The player's UUID - * @return The current scale factor (1.0 = normal size) - */ - public static float getCurrentScale(UUID playerUUID) { - // Safety checks - disable scaling in minigames or if UUID is null - // isSafeGameMode() returns true when in a minigame (not lobby/housing/etc) - if (HypixelDetector.isSafeGameMode() || playerUUID == null) { - return ScaleConstants.DEFAULT_SCALE; - } - - // Check cache first - Float cachedScale = currentScales.get(playerUUID); - if (cachedScale != null) { - return cachedScale; - } - - // Determine and cache the scale - float scale = determineScale(playerUUID); - currentScales.put(playerUUID, scale); - targetScales.put(playerUUID, scale); - - return scale; - } - - /** - * Updates all scales every tick with optional smooth interpolation. - * Should be called every client tick. - */ - public static void tick() { - refreshConfigBasedScales(); - - if (ScaleMeConfig.smoothScaling) { - applySmoothScaling(); - } else { - applyInstantScaling(); - } - } - - // ===== Preset Management ===== - - /** - * Adds or updates a player preset. - * Automatically saves to disk and refreshes all scales. - * - * @param preset The preset to add or update - */ - public static void addOrUpdatePreset(PlayerPreset preset) { - if (!isValidPreset(preset)) { - LOGGER.warn("Attempted to add invalid preset: {}", preset); - return; - } - - // Validate before saving - try { - preset.validateForSave(); - } catch (IllegalArgumentException e) { - LOGGER.error("Preset validation failed: {}", e.getMessage()); - throw new IllegalArgumentException("Cannot save invalid preset: " + e.getMessage(), e); - } - - // Remove existing preset with same identifier - removePresetQuiet(preset.identifier); - - presets.add(preset); - savePresets(); - refreshAllScales(); - } - - /** - * Removes a preset by identifier. - * - * @param identifier The preset identifier (UUID or username) - * @return true if a preset was removed - */ - public static boolean removePreset(String identifier) { - if (identifier == null || identifier.isEmpty()) { - return false; - } - - boolean removed = presets.removeIf(p -> - p.identifier.equalsIgnoreCase(identifier) - ); - - if (removed) { - savePresets(); - refreshAllScales(); - } - - return removed; - } - - /** - * Gets a preset by identifier. - * - * @param identifier The preset identifier - * @return The preset, or null if not found - */ - public static PlayerPreset getPreset(String identifier) { - if (identifier == null || identifier.isEmpty()) { - return null; - } - - return presets.stream() - .filter(p -> p.identifier.equalsIgnoreCase(identifier)) - .findFirst() - .orElse(null); - } - - /** - * Returns a defensive copy of all presets. - * - * @return List of all presets - */ - public static List getAllPresets() { - return new ArrayList<>(presets); - } - - /** - * Enables or disables a preset. - * - * @param identifier The preset identifier - * @param enabled Whether the preset should be enabled - * @return true if the preset was found and updated - */ - public static boolean setPresetEnabled(String identifier, boolean enabled) { - PlayerPreset preset = getPreset(identifier); - if (preset == null) { - return false; - } - - preset.enabled = enabled; - savePresets(); - refreshAllScales(); - return true; - } - - // ===== File I/O ===== - - /** - * Loads presets from disk. - * Creates an empty preset file if none exists. - */ - public static void loadPresets() { - presets.clear(); - - if (!presetFile.exists()) { - LOGGER.info("No preset file found, creating empty file"); - savePresets(); - return; - } - - try (FileReader reader = new FileReader(presetFile)) { - Type listType = new TypeToken>(){}.getType(); - List loaded = gson.fromJson(reader, listType); - - if (loaded != null) { - presets.addAll(loaded); - LOGGER.info("Loaded {} preset(s) from disk", presets.size()); - } - } catch (Exception e) { - LOGGER.error("Failed to load presets: {}", e.getMessage(), e); - } - } - - /** - * Saves all presets to disk. - */ - public static void savePresets() { - try { - // Ensure directory exists - File parentDir = presetFile.getParentFile(); - if (parentDir != null && !parentDir.exists()) { - parentDir.mkdirs(); - } - - try (FileWriter writer = new FileWriter(presetFile)) { - gson.toJson(presets, writer); - } - } catch (IOException e) { - LOGGER.error("Failed to save presets: {}", e.getMessage(), e); - } - } - - // ===== Private Helper Methods ===== - - /** - * Determines the appropriate scale for a player based on priority. - */ - private static float determineScale(UUID playerUUID) { - MinecraftClient client = MinecraftClient.getInstance(); - - // Get player name for username-based preset matching - String playerName = getPlayerName(playerUUID, client); - - // Priority 1: Check enabled presets - for (PlayerPreset preset : presets) { - if (preset.matchesPlayer(playerUUID, playerName)) { - return ScaleConstants.clampScale(preset.scale); - } - } - - // Priority 2: Own player scale from config - if (isOwnPlayer(playerUUID, client)) { - return ScaleConstants.clampScale(ScaleMeConfig.ownPlayerScale); - } - - // Priority 3: Default "other players" scale from config (if enabled) - if (ScaleMeConfig.enableOtherPlayersScaling) { - return ScaleConstants.clampScale(ScaleMeConfig.otherPlayersScale); - } - - // If other players scaling is disabled, return default (no scaling) - return ScaleConstants.DEFAULT_SCALE; - } - - /** - * Applies smooth scale interpolation. - */ - private static void applySmoothScaling() { - for (var entry : targetScales.entrySet()) { - UUID uuid = entry.getKey(); - float target = entry.getValue(); - float current = currentScales.getOrDefault(uuid, target); - - float smoothed = interpolateScale(current, target); - currentScales.put(uuid, smoothed); - } - } - - /** - * Applies instant scale changes (no interpolation). - */ - private static void applyInstantScaling() { - currentScales.putAll(targetScales); - } - - /** - * Smoothly interpolates between current and target scale. - */ - private static float interpolateScale(float current, float target) { - float difference = target - current; - - if (Math.abs(difference) <= SMOOTH_THRESHOLD) { - return target; // Close enough, snap to target - } - - return current + (difference * SMOOTH_INTERPOLATION_SPEED); - } - - /** - * Refreshes all target scales from config and presets. - */ - private static void refreshAllScales() { - refreshConfigBasedScales(); - refreshPresetBasedScales(); - cleanupStaleScales(); - } - - /** - * Updates scales for own player and global "other players" default. - */ - private static void refreshConfigBasedScales() { - MinecraftClient client = MinecraftClient.getInstance(); - UUID ownUUID = client.player != null ? client.player.getUuid() : null; - - // Update own player scale - if (ownUUID != null) { - targetScales.put(ownUUID, ScaleMeConfig.ownPlayerScale); - } - - // Update other players without presets (only if enabled) - if (ScaleMeConfig.enableOtherPlayersScaling) { - updateOtherPlayersScale(ownUUID); - } else { - // If disabled, remove scales for players without presets - removeNonPresetPlayerScales(ownUUID); - } - } - - /** - * Updates target scales for all enabled presets. - */ - private static void refreshPresetBasedScales() { - for (PlayerPreset preset : presets) { - if (!preset.enabled) { - continue; - } - - UUID resolvedUUID = preset.resolveToUUID(); - if (resolvedUUID != null) { - targetScales.put(resolvedUUID, preset.scale); - } - } - } - - /** - * Updates scales for players without presets to use the global default. - */ - private static void updateOtherPlayersScale(UUID ownUUID) { - float defaultScale = ScaleMeConfig.otherPlayersScale; - - for (UUID uuid : targetScales.keySet()) { - // Skip own player - if (uuid.equals(ownUUID)) { - continue; - } - - // Skip if player has an enabled preset - if (hasEnabledPreset(uuid)) { - continue; - } - - // Apply global default - targetScales.put(uuid, defaultScale); - } - } - - /** - * Removes scales for players without presets (when other players scaling is disabled). - */ - private static void removeNonPresetPlayerScales(UUID ownUUID) { - // Collect UUIDs to remove - List toRemove = new ArrayList<>(); - - for (UUID uuid : targetScales.keySet()) { - // Keep own player - if (uuid.equals(ownUUID)) { - continue; - } - - // Keep if player has an enabled preset - if (hasEnabledPreset(uuid)) { - continue; - } - - // Remove this player's scale - toRemove.add(uuid); - } - - // Remove collected UUIDs - for (UUID uuid : toRemove) { - targetScales.remove(uuid); - currentScales.remove(uuid); - } - } - - /** - * Removes scales for players no longer in the target map. - */ - private static void cleanupStaleScales() { - currentScales.keySet().retainAll(targetScales.keySet()); - } - - /** - * Checks if a player has an enabled preset. - */ - private static boolean hasEnabledPreset(UUID uuid) { - String uuidString = uuid.toString(); - return presets.stream() - .anyMatch(p -> p.enabled && - p.isUUID() && - p.identifier.equalsIgnoreCase(uuidString)); - } - - /** - * Gets the player name from the player list or current player. - */ - private static String getPlayerName(UUID playerUUID, MinecraftClient client) { - if (client.getNetworkHandler() != null) { - var entry = client.getNetworkHandler().getPlayerListEntry(playerUUID); - if (entry != null) { - //? if >=1.21.9 { - /*return entry.getProfile().name(); - *///?} else { - return entry.getProfile().getName(); - //?} - } - } - return null; - } - - /** - * Checks if a UUID belongs to the current player. - */ - private static boolean isOwnPlayer(UUID playerUUID, MinecraftClient client) { - return client.player != null && playerUUID.equals(client.player.getUuid()); - } - - /** - * Validates a preset before adding it. - */ - private static boolean isValidPreset(PlayerPreset preset) { - return preset != null && - preset.identifier != null && - !preset.identifier.isEmpty() && - ScaleConstants.isValidScale(preset.scale); - } - - /** - * Removes a preset without triggering save/refresh (internal use). - */ - private static void removePresetQuiet(String identifier) { - presets.removeIf(p -> p.identifier.equalsIgnoreCase(identifier)); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerUUIDResolver.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerUUIDResolver.java deleted file mode 100644 index f377caa..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/PlayerUUIDResolver.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.minecraft.client.MinecraftClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Resolves Minecraft player UUIDs from usernames. - *

- * Resolution priority: - *

    - *
  1. Cache lookup
  2. - *
  3. Current player check
  4. - *
  5. Player list check (online players)
  6. - *
  7. Direct UUID parsing (if input is already a UUID)
  8. - *
  9. Mojang API query (fallback)
  10. - *
- *

- * Thread-safe with caching to minimize API calls. - */ -public class PlayerUUIDResolver { - private static final Logger LOGGER = LoggerFactory.getLogger(PlayerUUIDResolver.class); - - // Mojang API configuration - private static final String MOJANG_API_URL = "https://api.mojang.com/users/profiles/minecraft/"; - private static final int HTTP_NOT_FOUND = 204; - - // UUID format pattern - private static final String UUID_INSERTION_PATTERN = "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})"; - private static final String UUID_FORMAT = "$1-$2-$3-$4-$5"; - - // Cache for resolved UUIDs - private static final ConcurrentHashMap uuidCache = new ConcurrentHashMap<>(); - - /** - * Resolves a player's UUID from their username. - *

- * The input can be: - *

    - *
  • A player username (e.g., "Notch")
  • - *
  • A UUID string (with or without hyphens)
  • - *
- * - * @param playerName The player name or UUID string - * @return The resolved UUID, or null if resolution fails - */ - public static UUID resolvePlayerUUID(String playerName) { - if (isInvalidInput(playerName)) { - return null; - } - - String normalizedName = normalizeName(playerName); - return uuidCache.computeIfAbsent(normalizedName, PlayerUUIDResolver::resolveUUIDInternal); - } - - /** - * Clears the UUID cache. - * Useful for testing or when player data changes. - */ - public static void clearCache() { - uuidCache.clear(); - LOGGER.debug("UUID cache cleared"); - } - - /** - * Gets the current cache size. - * - * @return Number of cached UUID entries - */ - public static int getCacheSize() { - return uuidCache.size(); - } - - // ===== Private Helper Methods ===== - - /** - * Internal resolution method that tries all available sources. - */ - private static UUID resolveUUIDInternal(String normalizedName) { - MinecraftClient client = MinecraftClient.getInstance(); - - // Priority 1: Check if it's the current player - UUID ownPlayerUUID = checkCurrentPlayer(normalizedName, client); - if (ownPlayerUUID != null) { - return ownPlayerUUID; - } - - // Priority 2: Check player list (online players) - UUID playerListUUID = checkPlayerList(normalizedName, client); - if (playerListUUID != null) { - return playerListUUID; - } - - // Priority 3: Try parsing as UUID - UUID parsedUUID = tryParseUUID(normalizedName); - if (parsedUUID != null) { - return parsedUUID; - } - - // Priority 4: Query Mojang API (fallback) - return queryMojangAPI(normalizedName); - } - - /** - * Checks if the name matches the current player. - */ - private static UUID checkCurrentPlayer(String normalizedName, MinecraftClient client) { - if (client.player == null) { - return null; - } - - String playerName = client.player.getName().getString().toLowerCase(); - if (normalizedName.equals(playerName)) { - return client.player.getUuid(); - } - - return null; - } - - /** - * Checks the player list for online players. - */ - private static UUID checkPlayerList(String normalizedName, MinecraftClient client) { - if (client.getNetworkHandler() == null) { - return null; - } - - return client.getNetworkHandler().getPlayerList().stream() - //? if >=1.21.9 { - /*.filter(entry -> entry.getProfile().name().equalsIgnoreCase(normalizedName)) - .map(entry -> entry.getProfile().id()) - *///?} else { - .filter(entry -> entry.getProfile().getName().equalsIgnoreCase(normalizedName)) - .map(entry -> entry.getProfile().getId()) - //?} - .findFirst() - .orElse(null); - } - - /** - * Attempts to parse the input as a UUID string. - */ - private static UUID tryParseUUID(String input) { - try { - return UUID.fromString(input); - } catch (IllegalArgumentException e) { - // Not a valid UUID format - return null; - } - } - - /** - * Queries the Mojang API to resolve a username to UUID. - */ - private static UUID queryMojangAPI(String playerName) { - try { - String apiUrl = MOJANG_API_URL + playerName; - HttpURLConnection connection = createConnection(apiUrl); - - // Check for "not found" response - if (connection.getResponseCode() == HTTP_NOT_FOUND) { - LOGGER.info("Player '{}' not found in Mojang database", playerName); - return null; - } - - String responseJson = readResponse(connection); - return parseUUIDFromJson(responseJson, playerName); - - } catch (Exception e) { - LOGGER.error("Failed to resolve UUID for '{}': {}", playerName, e.getMessage()); - return null; - } - } - - /** - * Creates an HTTP connection to the Mojang API. - */ - private static HttpURLConnection createConnection(String url) throws Exception { - HttpURLConnection connection = (HttpURLConnection) - java.net.URI.create(url).toURL().openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - return connection; - } - - /** - * Reads the response from an HTTP connection. - */ - private static String readResponse(HttpURLConnection connection) throws Exception { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(connection.getInputStream()))) { - - StringBuilder response = new StringBuilder(); - String line; - - while ((line = reader.readLine()) != null) { - response.append(line); - } - - return response.toString(); - } - } - - /** - * Parses a UUID from the Mojang API JSON response. - */ - private static UUID parseUUIDFromJson(String jsonResponse, String playerName) { - try { - JsonObject json = JsonParser.parseString(jsonResponse).getAsJsonObject(); - String uuidWithoutHyphens = json.get("id").getAsString(); - String formattedUUID = formatUUID(uuidWithoutHyphens); - - return UUID.fromString(formattedUUID); - } catch (Exception e) { - LOGGER.error("Failed to parse UUID from Mojang response for '{}': {}", - playerName, e.getMessage()); - return null; - } - } - - /** - * Formats a UUID string without hyphens to standard format with hyphens. - * - * @param uuidWithoutHyphens UUID string without hyphens (32 characters) - * @return Formatted UUID string with hyphens - */ - private static String formatUUID(String uuidWithoutHyphens) { - return uuidWithoutHyphens.replaceFirst(UUID_INSERTION_PATTERN, UUID_FORMAT); - } - - /** - * Validates input and checks if it's usable. - */ - private static boolean isInvalidInput(String input) { - return input == null || input.isBlank(); - } - - /** - * Normalizes a player name for cache key. - */ - private static String normalizeName(String playerName) { - return playerName.trim().toLowerCase(); - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleConstants.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleConstants.java deleted file mode 100644 index 856892c..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleConstants.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -/** - * Centralized constants for the ScaleMe mod. - * Contains all magic numbers, scale ranges, and default values. - */ -public final class ScaleConstants { - - // Prevent instantiation - private ScaleConstants() { - throw new AssertionError("Constants class should not be instantiated"); - } - - // ===== Scale Ranges ===== - public static final float MIN_SCALE = 0.1f; - public static final float MAX_SCALE = 3.0f; - public static final float DEFAULT_SCALE = 1.0f; - - // ===== Scale Thresholds ===== - public static final float SMALL_SCALE_THRESHOLD = 0.8f; - public static final float LARGE_SCALE_THRESHOLD = 1.5f; - - // ===== Animation Constants ===== - public static final int DEFAULT_SWING_DURATION = 6; - public static final int MIN_SWING_DURATION = 1; - public static final int MAX_SWING_DURATION = 60; - public static final float SWING_BOBBING_DISABLED_VALUE = 1.0f; - - // ===== UI Constants ===== - public static final int PLAYER_HEAD_SIZE = 24; - public static final int PLAYER_HEAD_CONTAINER_PADDING = 4; - public static final int SCALE_BAR_WIDTH = 40; - public static final int SCALE_BAR_HEIGHT = 3; - - // ===== UUID Pattern ===== - public static final String UUID_REGEX = "^[0-9a-fA-F\\-]{36}$"; - public static final String USERNAME_REGEX = "^[a-zA-Z0-9_]{3,16}$"; - - // ===== Validation Messages ===== - public static final String ERROR_INVALID_SCALE = "Scale must be between %.2f and %.2f"; - public static final String ERROR_NULL_IDENTIFIER = "Identifier cannot be null or empty"; - public static final String ERROR_INVALID_USERNAME = "Username must be 3-16 characters (alphanumeric and underscores only)"; - - /** - * Validates if a scale value is within acceptable range. - * @param scale The scale value to validate - * @return true if valid, false otherwise - */ - public static boolean isValidScale(float scale) { - return scale >= MIN_SCALE && scale <= MAX_SCALE && !Float.isNaN(scale) && !Float.isInfinite(scale); - } - - /** - * Clamps a scale value to the valid range. - * @param scale The scale value to clamp - * @return Clamped scale value - */ - public static float clampScale(float scale) { - if (Float.isNaN(scale) || Float.isInfinite(scale)) { - return DEFAULT_SCALE; - } - return Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale)); - } - - /** - * Checks if a scale value should be considered "tiny". - * @param scale The scale value - * @return true if scale is below small threshold - */ - public static boolean isTinyScale(float scale) { - return scale < SMALL_SCALE_THRESHOLD; - } - - /** - * Checks if a scale value should be considered "large". - * @param scale The scale value - * @return true if scale is above large threshold - */ - public static boolean isLargeScale(float scale) { - return scale > LARGE_SCALE_THRESHOLD; - } - - /** - * Checks if a scale value is effectively default (no scaling). - * @param scale The scale value - * @return true if scale is effectively 1.0 - */ - public static boolean isDefaultScale(float scale) { - return Math.abs(scale - DEFAULT_SCALE) < 0.001f; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleManager.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleManager.java deleted file mode 100644 index 80fb778..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleManager.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; - -import java.util.UUID; - -/** - * Simple facade for player scaling. All logic moved to PlayerPresetManager. - * This class exists only for backwards compatibility and clean API. - */ -public class ScaleManager { - - /** - * Initialize the scaling system. - */ - public static void init() { - PlayerPresetManager.init(); - } - - /** - * Update all scales every tick. - */ - public static void tick() { - PlayerPresetManager.tick(); - } - - /** - * Get current scale for a player. - */ - public static float getCurrentScale(UUID playerUUID) { - if (playerUUID == null) { - return ScaleConstants.DEFAULT_SCALE; - } - return PlayerPresetManager.getCurrentScale(playerUUID); - } - /** Returns NPC scale with safety check including dungeons detection. */ - public static float getNpcScale() { - // Only apply scaling when it's safe (not in competitive modes or dungeons) - if (HypixelDetector.isSafeForNPCScaling()) return 1.0f; - return ScaleMeConfig.npcPlayerScale; - } - - /** Returns Villager NPC scale with safety check including dungeons detection. */ - public static float getVillagerNpcScale() { - // Only apply scaling when it's safe (not in competitive modes or dungeons) - if (HypixelDetector.isSafeForNPCScaling()) return 1.0f; - return ScaleMeConfig.villagerNpcScale; - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleTransformer.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleTransformer.java deleted file mode 100644 index cfe7055..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/ScaleTransformer.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.RotationAxis; - -/** - * Utility class for applying scale transformations to matrix stacks. - * Centralizes all transformation logic to ensure consistency. - */ -public final class ScaleTransformer { - - private ScaleTransformer() { - throw new AssertionError("Utility class should not be instantiated"); - } - - /** - * Applies a uniform scale transformation if the scale is not default. - * @param matrices The matrix stack to transform - * @param scale The scale factor to apply - */ - public static void applyScale(MatrixStack matrices, float scale) { - if (matrices == null) { - return; - } - - if (!ScaleConstants.isDefaultScale(scale)) { - float clampedScale = ScaleConstants.clampScale(scale); - matrices.scale(clampedScale, clampedScale, clampedScale); - } - } - - /** - * Applies rotation transformations in the correct order (pitch, yaw, roll). - * @param matrices The matrix stack to transform - * @param pitch Pitch rotation in degrees - * @param yaw Yaw rotation in degrees - * @param roll Roll rotation in degrees - */ - public static void applyRotations(MatrixStack matrices, float pitch, float yaw, float roll) { - if (matrices == null) { - return; - } - - if (pitch != 0.0f) { - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch)); - } - if (yaw != 0.0f) { - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw)); - } - if (roll != 0.0f) { - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(roll)); - } - } - - /** - * Applies position translation, accounting for scale. - * Translation is divided by scale to maintain consistent positioning. - * @param matrices The matrix stack to transform - * @param x X position offset - * @param y Y position offset - * @param z Z position offset - * @param scale The current scale factor (used to adjust translation) - */ - public static void applyTranslation(MatrixStack matrices, float x, float y, float z, float scale) { - if (matrices == null) { - return; - } - - if (x == 0.0f && y == 0.0f && z == 0.0f) { - return; - } - - float safeScale = Math.max(0.01f, scale); // Prevent division by zero - matrices.translate(x / safeScale, y / safeScale, z / safeScale); - } - - /** - * Applies complete transformation for held items: rotation, scale, and position. - * @param matrices The matrix stack to transform - * @param config Item transformation configuration - */ - public static void applyItemTransform(MatrixStack matrices, ItemTransformConfig config) { - if (matrices == null || config == null) { - return; - } - - // Apply rotations first - applyRotations(matrices, config.pitch, config.yaw, config.roll); - - // Apply scaling - applyScale(matrices, config.scale); - - // Apply position adjustments - applyTranslation(matrices, config.xPos, config.yPos, config.zPos, config.scale); - } - - /** - * Configuration class for item transformations. - * Encapsulates all transformation parameters. - */ - public static class ItemTransformConfig { - public final float pitch; - public final float yaw; - public final float roll; - public final float scale; - public final float xPos; - public final float yPos; - public final float zPos; - - public ItemTransformConfig(float pitch, float yaw, float roll, float scale, - float xPos, float yPos, float zPos) { - this.pitch = pitch; - this.yaw = yaw; - this.roll = roll; - this.scale = ScaleConstants.clampScale(scale); - this.xPos = xPos; - this.yPos = yPos; - this.zPos = zPos; - } - - /** - * Creates a configuration from the mod's config values. - */ - public static ItemTransformConfig fromConfig() { - return new ItemTransformConfig( - ScaleMeConfig.heldItemPitchRotation, - ScaleMeConfig.heldItemYawRotation, - ScaleMeConfig.heldItemRollRotation, - ScaleMeConfig.itemScale, - ScaleMeConfig.heldItemXPosition, - ScaleMeConfig.heldItemYPosition, - ScaleMeConfig.heldItemZPosition - ); - } - - /** - * Checks if this configuration requires any transformation. - */ - public boolean hasTransformations() { - return pitch != 0.0f || yaw != 0.0f || roll != 0.0f || - !ScaleConstants.isDefaultScale(scale) || - xPos != 0.0f || yPos != 0.0f || zPos != 0.0f; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/client/util/VillagerEntityRenderStateAccessor.java b/src/main/java/com/github/kd_gaming1/scaleme/client/util/VillagerEntityRenderStateAccessor.java deleted file mode 100644 index 1d3f3b9..0000000 --- a/src/main/java/com/github/kd_gaming1/scaleme/client/util/VillagerEntityRenderStateAccessor.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.kd_gaming1.scaleme.client.util; - -import net.minecraft.entity.passive.VillagerEntity; - -public interface VillagerEntityRenderStateAccessor { - void scaleme$setVillagerEntity(VillagerEntity villager); - VillagerEntity scaleme$getVillagerEntity(); -} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d4ed555..03c30a7 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -14,7 +14,7 @@ "com.github.kd_gaming1.scaleme.client.ScalemeClient" ], "main": [ - "com.github.kd_gaming1.scaleme.Scaleme" + "com.github.kd_gaming1.scaleme.ScaleMe" ] }, "mixins": [ diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index da6ca5e..57ac966 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -10,7 +10,7 @@ stonecutter tasks { order("publishCurseforge") } -stonecutter active "1.21.5" +stonecutter active "1.21.10" stonecutter parameters { swaps["mod_version"] = "\"" + property("mod.version") + "\";" diff --git a/versions/1.21.10/gradle.properties b/versions/1.21.10/gradle.properties index b8c9cb1..e243b59 100644 --- a/versions/1.21.10/gradle.properties +++ b/versions/1.21.10/gradle.properties @@ -1,9 +1,8 @@ -deps.yarn_mappings=1.21.10+build.2 -deps.fabric_api=0.138.0+1.21.10 +deps.fabric_api=0.138.4+1.21.10 deps.midnightlib_version=1.9.2+1.21.10-fabric +deps.hm_api_version=1.0.1+1.21.2 deps.modmenu_version=16.0.0-rc.1 -deps.owo_version=0.12.24+1.21.9 mod.mc_dep=>=1.21.9 <=1.21.10 mod.mc_title=1.21.10 diff --git a/versions/1.21.11/gradle.properties b/versions/1.21.11/gradle.properties index 6201edf..5c2dcd5 100644 --- a/versions/1.21.11/gradle.properties +++ b/versions/1.21.11/gradle.properties @@ -1,9 +1,8 @@ -deps.yarn_mappings=1.21.11+build.4 -deps.fabric_api=0.141.2+1.21.11 +deps.fabric_api=0.141.3+1.21.11 deps.midnightlib_version=1.9.2+1.21.11-fabric +deps.hm_api_version=1.0.1+1.21.2 deps.modmenu_version=17.0.0-beta.2 -deps.owo_version=0.13.0+1.21.11 mod.mc_dep=1.21.11 mod.mc_title=1.21.11 diff --git a/versions/1.21.5/gradle.properties b/versions/1.21.5/gradle.properties deleted file mode 100644 index af692b2..0000000 --- a/versions/1.21.5/gradle.properties +++ /dev/null @@ -1,10 +0,0 @@ -deps.yarn_mappings=1.21.5+build.1 -deps.fabric_api=0.128.2+1.21.5 - -deps.midnightlib_version=1.9.2+1.21.5-fabric -deps.modmenu_version=14.0.0-rc.2 -deps.owo_version=0.12.21+1.21.5 - -mod.mc_dep=1.21.5 -mod.mc_title=1.21.5 -mod.mc_targets=1.21.5 \ No newline at end of file diff --git a/versions/1.21.8/gradle.properties b/versions/1.21.8/gradle.properties deleted file mode 100644 index 48f15dd..0000000 --- a/versions/1.21.8/gradle.properties +++ /dev/null @@ -1,10 +0,0 @@ -deps.yarn_mappings=1.21.8+build.1 -deps.fabric_api=0.136.0+1.21.8 - -deps.midnightlib_version=1.7.5+1.21.6-fabric -deps.modmenu_version=15.0.0 -deps.owo_version=0.12.23+1.21.8 - -mod.mc_dep=>=1.21.6 <=1.21.8 -mod.mc_title=1.21.8 -mod.mc_targets=1.21.6 1.21.7 1.21.8 \ No newline at end of file From 34d0aac7b4dd4fe77453719939b1e846c85d9b6d Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:48:09 +0100 Subject: [PATCH 02/16] feat: item transformation and animation options --- build.gradle.kts | 2 + .../github/kd_gaming1/scaleme/ScaleMe.java | 5 +- .../scaleme/config/ScaleMeConfig.java | 139 ++++++------------ .../mixin/ItemInHandRendererMixin.java | 34 +++++ .../scaleme/mixin/LayerRenderStateMixin.java | 60 ++++++++ .../scaleme/mixin/LivingEntityMixin.java | 49 ++++++ .../resources/assets/scaleme/lang/en_us.json | 136 ++++++++--------- src/main/resources/fabric.mod.json | 8 +- src/main/resources/scaleme.mixins.json | 18 +-- 9 files changed, 262 insertions(+), 189 deletions(-) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java diff --git a/build.gradle.kts b/build.gradle.kts index b38d070..97d729f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -71,6 +71,7 @@ tasks { inputs.property("version", project.property("mod.version")) inputs.property("minecraft", project.property("mod.mc_dep")) inputs.property("fabricloader", project.property("deps.fabric_loader")) + inputs.property("midnightlib", project.property("deps.midnightlib_version")) inputs.property("fabric_api", project.property("deps.fabric_api")) inputs.property("hm_api", project.property("deps.hm_api_version")) @@ -80,6 +81,7 @@ tasks { "version" to project.property("mod.version"), "minecraft" to project.property("mod.mc_dep"), "fabricloader" to project.property("deps.fabric_loader"), + "midnightlib" to project.property("deps.midnightlib_version"), "fabric_api" to project.property("deps.fabric_api"), "hm_api" to project.property("deps.hm_api_version"), ) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 137be0b..ff65525 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -1,5 +1,7 @@ package com.github.kd_gaming1.scaleme; +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import eu.midnightdust.lib.config.MidnightConfig; import net.fabricmc.api.ClientModInitializer; import org.slf4j.Logger; @@ -7,9 +9,10 @@ public class ScaleMe implements ClientModInitializer { public static final String MOD_ID = "scaleme"; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final Logger LOGGER = LoggerFactory.getLogger("Scale Me"); @Override public void onInitializeClient() { + MidnightConfig.init(MOD_ID, ScaleMeConfig.class); } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 71a8241..6afa85c 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -4,129 +4,78 @@ public class ScaleMeConfig extends MidnightConfig { public static final String SCALING = "scaling"; - public static final String ITEMSCALING = "item scaling"; + public static final String HAND = "hand_item"; + public static final String ANIM = "animation"; public static final String VIEW = "view"; - // Player Scaling - @Comment(category = SCALING, name = "Adjust the visual size of your own player model") - public static Comment ownPlayerDescription; + // ── Hand Item Transform ───────────────────────────────────────────────── - @Entry(category = SCALING, name = "Enable Own Player Scaling") - public static boolean enableOwnPlayerScaling = false; + @Comment(category = HAND) + public static Comment handDesc; - @Entry(category = SCALING, name = "Own Player Scale", isSlider = true, min = 0.1f, max = 3.0f) - public static float ownPlayerScale = 1.0f; + @Entry(category = HAND) + public static boolean enableHandItemTransform = false; + @Comment(category = HAND, centered = true) + public static Comment spacer1; - @Comment(category = SCALING, name = "Adjust the visual size of other players") - public static Comment playersDescription; + @Entry(category = HAND, name = "Scale", isSlider = true, min = 0.1f, max = 4f, precision = 1000) + public static float itemScale = 1f; - @Entry(category = SCALING, name = "Enable Scaling for Other Players") - public static boolean enableOtherPlayersScaling = false; + @Comment(category = HAND, centered = true) + public static Comment spacer2; - @Entry(category = SCALING, name = "Other Players Scale", isSlider = true, min = 0.1f, max = 3.0f) - public static float otherPlayersScale = 1.0f; + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationX = 0f; - @Comment(category = SCALING, name = "Adjust the visual size of NPC players") - public static Comment npcDescription; + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationY = 0f; - @Entry(category = SCALING, name = "Enable Hypixel NPC Scaling") - public static boolean enableNpcScaling = false; + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationZ = 0f; - @Entry(category = SCALING, name = "NPC Scale", isSlider = true, min = 0.1f, max = 3.0f) - public static float npcPlayerScale = 1.0f; + @Comment(category = HAND, centered = true) + public static Comment spacer4; - @Comment(category = SCALING, name = "Adjust the visual size of Villager NPC Scaling") - public static Comment villagerNpcDescription; + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationX = 0f; - @Entry(category = SCALING, name = "Enable Hypixel Villager NPC Scaling") - public static boolean enableVillagerNpcScaling = false; + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationY = 0f; - @Entry(category = SCALING, name = "Villager NPC Scale", isSlider = true, min = 0.1f, max = 3.0f) - public static float villagerNpcScale = 1.0f; + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationZ = 0f; - @Comment(category = SCALING, name = "Make changes between scale levels appear smooth") - public static Comment smoothedScalingDescription; + @Comment(category = ANIM) + public static Comment animDesc; - @Entry(category = SCALING, name = "Smooth Scaling") - public static boolean smoothScaling = true; + @Entry(category = ANIM) + public static boolean enableAnimOverrides = false; - /* - TODO: Add Quick Add Button to chat behind players - @Entry(category = PLAYERS, name = "Show Quick Add Button") - public static boolean showQuickAddButton = true; - */ + @Entry(category = ANIM) + public static boolean disableSwingBobbing = false; - // Item Scaling + @Entry(category = ANIM) + public static boolean ignoreSwingSpeedEffects = false; - @Comment(category = ITEMSCALING, name = "Adjust the visual size of items and there held positions and animation") - public static Comment itemScalingDescription; + @Entry(category = ANIM, isSlider = true, min = 0.1f, max = 2f) + public static float swingAnimationSpeed = 1f; - @Entry(category = ITEMSCALING, name = "Enable Item Scale & Position") - public static boolean enableItemScaleAndPosition = false; - - /* - TODO: Add option to only affect weapons and tools - @Entry(category = ITEMSCALING, name = "Only affect weapons and tools") - public static boolean onlyAffectWeaponsAndTools = false; - */ - - @Entry(category = ITEMSCALING, name = "Item Scale", isSlider = true, min = 0.05f, max = 3.0f) - public static float itemScale = 1.0f; - - @Comment(category = ITEMSCALING, name = "Adjust the position of your held item") - public static Comment heldItemPositionDescription; - - @Entry(category = ITEMSCALING, name = "Held Item X Position", isSlider = true, min = -2.0f, max = 2.0f) - public static float heldItemXPosition = 0.0f; - - @Entry(category = ITEMSCALING, name = "Held Item Y Position", isSlider = true, min = -2.0f, max = 2.0f) - public static float heldItemYPosition = 0.0f; - - @Entry(category = ITEMSCALING, name = "Held Item Z Position", isSlider = true, min = -2.0f, max = 2.0f) - public static float heldItemZPosition = 0.0f; - - @Comment(category = ITEMSCALING, name = "Adjust the Rotation of held item") - public static Comment heldItemRotationDescription; - - @Entry(category = ITEMSCALING, name = "Held Item Yaw Rotation", isSlider = true, min = -180.0f, max = 180.0f) - public static float heldItemYawRotation = 0.0f; - - @Entry(category = ITEMSCALING, name = "Held Item Pitch Rotation", isSlider = true, min = -90.0f, max = 90.0f) - public static float heldItemPitchRotation = 0.0f; - - @Entry(category = ITEMSCALING, name = "Held Item Roll Rotation", isSlider = true, min = -180.0f, max = 180.0f) - public static float heldItemRollRotation = 0.0f; - - @Comment(category = ITEMSCALING, name = "Adjust swing/animation speed of items") - public static Comment itemAnimationSpeedDescription; - - @Entry(category = ITEMSCALING, name = "Enable Item Swing Modifications") - public static boolean enableItemSwingModifications = false; - - @Entry(category = ITEMSCALING, name = "Ignore mining effects") - public static boolean ignoreMiningEffects = true; - - @Entry(category = ITEMSCALING, name = "Disable swing animation bobbing") - public static boolean disableSwingAnimationBobbing = true; - - @Entry(category = ITEMSCALING, name = "Item Animation Speed", isSlider = true, min = 0.05f, max = 3.0f) - public static float itemAnimationSpeed = 1.0f; - - @Entry(category = ITEMSCALING, name = "Disable Item Animation Completely") - public static boolean disableItemAnimation = false; + @Entry(category = ANIM) + public static boolean disableSwingAnimation = false; + // ── View ──────────────────────────────────────────────────────────────── // View (Crosshair + Camera) - @Comment(category = VIEW, name = "Configure crosshair and camera options") + @Comment(category = VIEW) public static Comment viewDescription; - @Entry(category = VIEW, name = "Show Crosshair in Third Person (Back)") + @Entry(category = VIEW) public static boolean enableCrosshairInThirdPerson = false; - @Entry(category = VIEW, name = "Show Crosshair in Third Person (Front)") + @Entry(category = VIEW) public static boolean enableCrosshairInThirdPersonFront = false; - @Entry(category = VIEW, name = "Disable Selfie Cam (Front View)") + @Entry(category = VIEW) public static boolean disableSelfieCam = false; } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java new file mode 100644 index 0000000..a9ffb41 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -0,0 +1,34 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.ItemInHandRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ItemInHandRenderer.class) +public class ItemInHandRendererMixin { + + @ModifyExpressionValue( + method = "tick()V", + at = @At( + value = "INVOKE", + //? if >=1.21.11 { + /*target = "Lnet/minecraft/client/player/LocalPlayer;getItemSwapScale(F)F" + *///?} else { + target = "Lnet/minecraft/client/player/LocalPlayer;getAttackStrengthScale(F)F" + //?} + ) + ) + private float scaleme$disableSwingBobbing_attackStrength(float original) { + if (!ScaleMeConfig.enableAnimOverrides) return original; + if (!ScaleMeConfig.disableSwingBobbing) return original; + + LocalPlayer p = Minecraft.getInstance().player; + if (p == null) return original; + + return 1.0F; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java new file mode 100644 index 0000000..b18985a --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java @@ -0,0 +1,60 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.mojang.blaze3d.vertex.PoseStack; + +import net.minecraft.client.renderer.SubmitNodeCollector; +import org.joml.Quaternionf; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(targets = "net.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState") +public class LayerRenderStateMixin { + + @Unique + private boolean scaleme$didPush = false; + + + @Inject(method = "submit", at = @At("HEAD")) + private void onSubmitHead(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { + if (!ScaleMeConfig.enableHandItemTransform) return; + poseStack.pushPose(); + scaleme$didPush = true; + PoseStack.Pose pose = poseStack.last(); + + float tx = ScaleMeConfig.itemTranslationX; + float ty = ScaleMeConfig.itemTranslationY; + float tz = ScaleMeConfig.itemTranslationZ; + if (tx != 0f || ty != 0f || tz != 0f) { + pose.translate(tx, ty, tz); + } + + float rx = ScaleMeConfig.itemRotationX; + float ry = ScaleMeConfig.itemRotationY; + float rz = ScaleMeConfig.itemRotationZ; + if (rx != 0f || ry != 0f || rz != 0f) { + pose.rotate(new Quaternionf().rotationXYZ( + rx * (float)(Math.PI / 180.0), + ry * (float)(Math.PI / 180.0), + rz * (float)(Math.PI / 180.0) + )); + } + + float s = ScaleMeConfig.itemScale; + if (s != 1f) { + pose.scale(s, s, s); + } + } + + @Inject(method = "submit", at = @At("RETURN")) + private void onSubmitReturn(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { + if (!ScaleMeConfig.enableHandItemTransform) return; + if (scaleme$didPush) { + poseStack.popPose(); + scaleme$didPush = false; + } + } +} diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java new file mode 100644 index 0000000..479ee17 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java @@ -0,0 +1,49 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.LivingEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(LivingEntity.class) +public class LivingEntityMixin { + + @Unique + private static final int DEFAULT_SWING_DURATION = 6; + + @Inject(method = "getCurrentSwingDuration", at = @At("RETURN"), cancellable = true) + private void modifySwingDuration(CallbackInfoReturnable cir) { + if (!ScaleMeConfig.enableAnimOverrides) return; // master toggle + if (!ScaleMeConfig.ignoreSwingSpeedEffects && ScaleMeConfig.swingAnimationSpeed == 1f) return; + + LivingEntity self = (LivingEntity)(Object)this; + if (!self.level().isClientSide() || self != Minecraft.getInstance().player) return; + + int duration = cir.getReturnValue(); + + if (ScaleMeConfig.ignoreSwingSpeedEffects) { + duration = DEFAULT_SWING_DURATION; + } + + if (ScaleMeConfig.swingAnimationSpeed != 1f) { + duration = Math.max(1, Math.round(duration / ScaleMeConfig.swingAnimationSpeed)); + } + + cir.setReturnValue(duration); + } + + @Inject(method = "getAttackAnim", at = @At("RETURN"), cancellable = true) + private void suppressAttackAnim(float partialTick, CallbackInfoReturnable cir) { + if (!ScaleMeConfig.enableAnimOverrides) return; // master toggle + if (!ScaleMeConfig.disableSwingAnimation) return; + + LivingEntity self = (LivingEntity)(Object)this; + if (self.level().isClientSide() && self == Minecraft.getInstance().player) { + cir.setReturnValue(0f); + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 6ae8b6d..f106be5 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -1,76 +1,64 @@ { - "scaleme.midnightconfig.title": "ScaleMe - Visual Scaling", - "scaleme.midnightconfig.category.scaling": "Player Scaling Controls", - "scaleme.midnightconfig.category.item scaling": "Item Scaling Controls", - "scaleme.midnightconfig.category.view": "View & Camera", - - "scaleme.midnightconfig.ownPlayerDescription.label": "Adjust the visual size of your own player model", - "scaleme.midnightconfig.ownPlayerScale.label": "Own Player Scale", - "scaleme.midnightconfig.ownPlayerScale.tooltip": "Controls how large or small your own player appears visually", - - "scaleme.midnightconfig.playersDescription.label": "Adjust the visual size of other players", - "scaleme.midnightconfig.enableOtherPlayersScaling.label": "Enable Scaling for Other Players", - "scaleme.midnightconfig.enableOtherPlayersScaling.tooltip": "Allow scaling of other players' models (visual only)", - "scaleme.midnightconfig.otherPlayersScale.label": "Other Players Scale", - "scaleme.midnightconfig.otherPlayersScale.tooltip": "Controls how large or small other players appear visually", - - "scaleme.midnightconfig.npcDescription.label": "Adjust the visual size of NPC players", - "scaleme.midnightconfig.enableNpcScaling.label": "Enable Hypixel NPC Scaling", - "scaleme.midnightconfig.enableNpcScaling.tooltip": "Allow scaling of Hypixel NPC player models (visual only)", - "scaleme.midnightconfig.npcPlayerScale.label": "NPC Scale", - "scaleme.midnightconfig.npcPlayerScale.tooltip": "Controls how large or small Hypixel NPC players appear visually", - - "scaleme.midnightconfig.villagerNpcDescription.label": "Adjust the visual size of Villager NPC Scaling", - "scaleme.midnightconfig.enableVillagerNpcScaling.label": "Enable Hypixel Villager NPC Scaling", - "scaleme.midnightconfig.enableVillagerNpcScaling.tooltip": "Allow scaling of Hypixel Villager NPC models (visual only)", - "scaleme.midnightconfig.villagerNpcScale.label": "Villager NPC Scale", - "scaleme.midnightconfig.villagerNpcScale.tooltip": "Controls how large or small Hypixel Villager NPCs appear visually", - - "scaleme.midnightconfig.smoothedScalingDescription.label": "Make changes between scale levels appear smooth", - "scaleme.midnightconfig.smoothScaling.label": "Smooth Scaling", - "scaleme.midnightconfig.smoothScaling.tooltip": "Gradually transition between scale changes for your own player", - - "scaleme.midnightconfig.itemScalingDescription.label": "Adjust the visual size of items and their held positions and animation", - "scaleme.midnightconfig.enableItemScaleAndPosition.label": "Enable Item Scale & Position", - "scaleme.midnightconfig.enableItemScaleAndPosition.tooltip": "Enable scaling and position adjustments for held items (does not affect animation speed).", - "scaleme.midnightconfig.onlyAffectWeaponsAndTools.label": "Only affect weapons and tools", - "scaleme.midnightconfig.onlyAffectWeaponsAndTools.tooltip": "Only scale weapons and tools, not all items. (Some weapons on Hypixel skyblock may not scale properly, as hypixel don't use a normal weapon or tool item for them.)", - "scaleme.midnightconfig.itemScale.label": "Item Scale", - "scaleme.midnightconfig.itemScale.tooltip": "Controls how large or small held items appear visually", - - "scaleme.midnightconfig.heldItemPositionDescription.label": "Adjust the position of your held item", - "scaleme.midnightconfig.heldItemXPosition.label": "Held Item X Position", - "scaleme.midnightconfig.heldItemXPosition.tooltip": "Adjust the X position of your held item. Higher values will move the item further to the right.", - "scaleme.midnightconfig.heldItemYPosition.label": "Held Item Y Position", - "scaleme.midnightconfig.heldItemYPosition.tooltip": "Adjust the Y position of your held item. Higher values will move the item further up.", - "scaleme.midnightconfig.heldItemZPosition.label": "Held Item Z Position", - "scaleme.midnightconfig.heldItemZPosition.tooltip": "Adjust the Z position of your held item. Higher values will move the item closer to you.", - - "scaleme.midnightconfig.heldItemRotationDescription.label": "Adjust the Rotation of held item", - "scaleme.midnightconfig.heldItemYawRotation.label": "Held Item Yaw Rotation", - "scaleme.midnightconfig.heldItemYawRotation.tooltip": "Adjust the yaw rotation of your held item. Higher values will rotate the item to the right.", - "scaleme.midnightconfig.heldItemPitchRotation.label": "Held Item Pitch Rotation", - "scaleme.midnightconfig.heldItemPitchRotation.tooltip": "Adjust the pitch rotation of your held item. Higher values will rotate the item towards you.", - "scaleme.midnightconfig.heldItemRollRotation.label": "Held Item Roll Rotation", - "scaleme.midnightconfig.heldItemRollRotation.tooltip": "Adjust the roll rotation of your held item. Higher values will roll the item to the left.", - - "scaleme.midnightconfig.itemAnimationSpeedDescription.label": "Adjust swing/animation speed of items", - "scaleme.midnightconfig.enableItemSwingModifications.label": "Enable Item Swing Modifications", - "scaleme.midnightconfig.enableItemSwingModifications.tooltip": "Enable modifications to the swing/animation of held items.", - "scaleme.midnightconfig.ignoreMiningEffects.label": "Ignore mining effects", - "scaleme.midnightconfig.ignoreMiningEffects.tooltip": "If enabled, ignores the mining effects increase to animation speed.", - "scaleme.midnightconfig.disableSwingAnimationBobbing.label": "Disable swing animation bobbing", - "scaleme.midnightconfig.disableSwingAnimationBobbing.tooltip": "If enabled, disables the bobbing effect during item swing animations. Only works if the option 'Item Swing Modifications' or 'Item Scale & Position' is enabled.", - "scaleme.midnightconfig.itemAnimationSpeed.label": "Item Animation Speed", - "scaleme.midnightconfig.itemAnimationSpeed.tooltip": "Controls the animation speed of held items, 1.0 is normal speed / disabled.", - "scaleme.midnightconfig.disableItemAnimation.label": "Disable Item Animation Completely", - "scaleme.midnightconfig.disableItemAnimation.tooltip": "If enabled, completely freezes the item swing animation.", - - "scaleme.midnightconfig.viewDescription.label": "Configure crosshair and camera options", - "scaleme.midnightconfig.enableCrosshairInThirdPerson.label": "Show Crosshair in Third Person (Back)", - "scaleme.midnightconfig.enableCrosshairInThirdPerson.tooltip": "Display the crosshair when viewing from behind your character (F5 once)", - "scaleme.midnightconfig.enableCrosshairInThirdPersonFront.label": "Show Crosshair in Third Person (Front)", - "scaleme.midnightconfig.enableCrosshairInThirdPersonFront.tooltip": "Display the crosshair when viewing from in front of your character (F5 twice)", - "scaleme.midnightconfig.disableSelfieCam.label": "Disable Selfie Cam (Front View)", - "scaleme.midnightconfig.disableSelfieCam.tooltip": "Prevent cycling to front-facing third person view when pressing F5" + "scaleme.midnightconfig.title": "ScaleMe", + + "scaleme.midnightconfig.category.hand_item": "Held Item", + "scaleme.midnightconfig.category.view": "Camera & Crosshair", + "scaleme.midnightconfig.category.scaling": "Scaling", + + "scaleme.midnightconfig.handDesc": "Adjust how your held item looks in your hand", + "scaleme.midnightconfig.spacer1": "", + "scaleme.midnightconfig.spacer2": "", + "scaleme.midnightconfig.spacer4": "", + + "scaleme.midnightconfig.enableHandItemTransform": "Enable Custom Item Transform", + "scaleme.midnightconfig.enableHandItemTransform.tooltip": "Turns on the custom held item positioning. Disable this to restore the default look and avoid conflicts with other mods.", + + "scaleme.midnightconfig.itemScale": "Item Size", + "scaleme.midnightconfig.itemScale.tooltip": "How big or small the item in your hand appears.\nSmaller values make it tiny, larger values make it huge.", + + "scaleme.midnightconfig.itemTranslationX": "Move Item Left / Right", + "scaleme.midnightconfig.itemTranslationX.tooltip": "Shifts the item in your hand to the left or right.\nNegative values move it left, positive values move it right.", + + "scaleme.midnightconfig.itemTranslationY": "Move Item Up / Down", + "scaleme.midnightconfig.itemTranslationY.tooltip": "Shifts the item in your hand up or down.\nNegative values move it down, positive values move it up.", + + "scaleme.midnightconfig.itemTranslationZ": "Move Item Closer / Further", + "scaleme.midnightconfig.itemTranslationZ.tooltip": "Shifts the item in your hand closer to or further from your screen.\nNegative values push it away, positive values bring it closer.", + + "scaleme.midnightconfig.itemRotationX": "Tilt Item Up / Down", + "scaleme.midnightconfig.itemRotationX.tooltip": "Tilts the item in your hand up or down.", + + "scaleme.midnightconfig.itemRotationY": "Rotate Item Left / Right", + "scaleme.midnightconfig.itemRotationY.tooltip": "Rotates the item in your hand to the left or right.", + + "scaleme.midnightconfig.itemRotationZ": "Spin Item Clockwise / Counterclockwise", + "scaleme.midnightconfig.itemRotationZ.tooltip": "Spins the item in your hand clockwise or counterclockwise.", + + "scaleme.midnightconfig.animDesc": "Adjust how your held item animates", + + "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes", + "scaleme.midnightconfig.enableAnimOverrides.tooltip": "Turns on the custom animation settings below. Disable this to keep the default Minecraft swing animations.", + + "scaleme.midnightconfig.disableSwingBobbing": "Stop Item Bobbing While Swinging", + "scaleme.midnightconfig.disableSwingBobbing.tooltip": "Prevents the item from bouncing up and down when you swing it.", + + "scaleme.midnightconfig.ignoreSwingSpeedEffects": "Ignore Speed Effects on Swing", + "scaleme.midnightconfig.ignoreSwingSpeedEffects.tooltip": "Potion effects like Haste or Mining Fatigue will no longer change how fast your arm swings.", + + "scaleme.midnightconfig.swingAnimationSpeed": "Swing Speed (1 = Disable/Default)", + "scaleme.midnightconfig.swingAnimationSpeed.tooltip": "How fast your arm swings when you attack or use an item.\nLower values slow it down, higher values speed it up.", + + "scaleme.midnightconfig.disableSwingAnimation": "Disable Swing Animation", + "scaleme.midnightconfig.disableSwingAnimation.tooltip": "Completely removes the arm swing animation. Your arm stays still when you click.", + + "scaleme.midnightconfig.viewDescription": "Control your crosshair and camera behaviour", + + "scaleme.midnightconfig.enableCrosshairInThirdPerson": "Show Crosshair Behind You", + "scaleme.midnightconfig.enableCrosshairInThirdPerson.tooltip": "Shows the crosshair when the camera is behind your character.", + + "scaleme.midnightconfig.enableCrosshairInThirdPersonFront": "Show Crosshair in Front View", + "scaleme.midnightconfig.enableCrosshairInThirdPersonFront.tooltip": "Shows the crosshair when the camera is facing your character from the front.", + + "scaleme.midnightconfig.disableSelfieCam": "Disable Front Camera View", + "scaleme.midnightconfig.disableSelfieCam.tooltip": "Removes the option to flip the camera to face your character." } \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 03c30a7..d8fbe9d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -11,9 +11,6 @@ "environment": "client", "entrypoints": { "client": [ - "com.github.kd_gaming1.scaleme.client.ScalemeClient" - ], - "main": [ "com.github.kd_gaming1.scaleme.ScaleMe" ] }, @@ -21,9 +18,8 @@ "scaleme.mixins.json" ], "depends": { - "fabricloader": ">=0.17.3", + "fabricloader": ">=${fabricloader}", "minecraft": "${minecraft}", - "midnightlib": ">=${midnightlib_version}", - "owo": ">=${owo_version}" + "midnightlib": ">=${midnightlib}" } } \ No newline at end of file diff --git a/src/main/resources/scaleme.mixins.json b/src/main/resources/scaleme.mixins.json index d8ac70d..6a05179 100644 --- a/src/main/resources/scaleme.mixins.json +++ b/src/main/resources/scaleme.mixins.json @@ -1,23 +1,15 @@ { "required": true, - "minVersion": "0.8", - "package": "com.github.kd_gaming1.scaleme.client.mixin", + "package": "com.github.kd_gaming1.scaleme.mixin", "compatibilityLevel": "JAVA_21", - "refmap": "scaleme.refmap.json", - "client": [ - "HeldItemRendererMixin", - "LivingEntityRendererMixin", - "MixinInGameHud", - "PerspectiveMixin", - "PlayerEntityRendererMixin", - "PlayerEntityRenderStateMixin", - "VillagerEntityRendererMixin", - "VillagerEntityRenderStateMixin" + "mixins": [ ], "injectors": { "defaultRequire": 1 }, - "mixins": [ + "client": [ + "ItemInHandRendererMixin", + "LayerRenderStateMixin", "LivingEntityMixin" ] } \ No newline at end of file From a8be5f55629a9413f8ce03a7b143bf46740fb0e6 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:24:50 +0100 Subject: [PATCH 03/16] feat: add separate off-hand item transform options and enhance animation controls --- .../scaleme/config/ScaleMeConfig.java | 48 ++++++++++-- .../mixin/ItemInHandRendererMixin.java | 73 +++++++++++++++++-- .../scaleme/mixin/LayerRenderStateMixin.java | 64 ++++++++-------- .../scaleme/mixin/LivingEntityMixin.java | 38 ++++++---- .../kd_gaming1/scaleme/util/HandContext.java | 23 ++++++ .../resources/assets/scaleme/lang/en_us.json | 30 +++++++- 6 files changed, 220 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 6afa85c..3e35c91 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -2,8 +2,8 @@ import eu.midnightdust.lib.config.MidnightConfig; +@SuppressWarnings("unused") public class ScaleMeConfig extends MidnightConfig { - public static final String SCALING = "scaling"; public static final String HAND = "hand_item"; public static final String ANIM = "animation"; public static final String VIEW = "view"; @@ -16,15 +16,15 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND) public static boolean enableHandItemTransform = false; + @Entry(category = HAND) + public static boolean enableSeparateHandTransforms = false; + @Comment(category = HAND, centered = true) public static Comment spacer1; @Entry(category = HAND, name = "Scale", isSlider = true, min = 0.1f, max = 4f, precision = 1000) public static float itemScale = 1f; - @Comment(category = HAND, centered = true) - public static Comment spacer2; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) public static float itemTranslationX = 0f; @@ -35,7 +35,7 @@ public class ScaleMeConfig extends MidnightConfig { public static float itemTranslationZ = 0f; @Comment(category = HAND, centered = true) - public static Comment spacer4; + public static Comment spacer2; @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationX = 0f; @@ -46,6 +46,43 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationZ = 0f; + // ── Offhand Item Transform ────────────────────────────────────────────── + + @Comment(category = HAND, centered = true) + public static Comment spacer3; + + @Comment(category = HAND) + public static Comment offhandDesc; + + @Comment(category = HAND, centered = true) + public static Comment spacer4; + + @Entry(category = HAND, isSlider = true, min = 0.1f, max = 4f, precision = 1000) + public static float itemScaleOffhand = 1f; + + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationXOffhand = 0f; + + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationYOffhand = 0f; + + @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + public static float itemTranslationZOffhand = 0f; + + @Comment(category = HAND, centered = true) + public static Comment spacer5; + + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationXOffhand = 0f; + + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationYOffhand = 0f; + + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float itemRotationZOffhand = 0f; + + // ── Animation ─────────────────────────────────────────────────────────── + @Comment(category = ANIM) public static Comment animDesc; @@ -66,7 +103,6 @@ public class ScaleMeConfig extends MidnightConfig { // ── View ──────────────────────────────────────────────────────────────── - // View (Crosshair + Camera) @Comment(category = VIEW) public static Comment viewDescription; diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java index a9ffb41..6c851e1 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -1,16 +1,30 @@ package com.github.kd_gaming1.scaleme.mixin; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.HandContext; + import com.llamalad7.mixinextras.injector.ModifyExpressionValue; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; +import com.mojang.blaze3d.vertex.PoseStack; + +import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; + 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; +/** Mixins into {@link ItemInHandRenderer} to support animation and hand context overrides. */ @Mixin(ItemInHandRenderer.class) public class ItemInHandRendererMixin { + /** + * Overrides the attack strength scale used during the swing animation tick. + * When swing bobbing is disabled, returns 1.0 to suppress the effect. + */ @ModifyExpressionValue( method = "tick()V", at = @At( @@ -26,9 +40,58 @@ public class ItemInHandRendererMixin { if (!ScaleMeConfig.enableAnimOverrides) return original; if (!ScaleMeConfig.disableSwingBobbing) return original; - LocalPlayer p = Minecraft.getInstance().player; - if (p == null) return original; - return 1.0F; } + + /** Populates {@link HandContext} with the current hand and its transform values before rendering. */ + @Inject(method = "renderArmWithItem", at = @At("HEAD")) + private void captureHand( + AbstractClientPlayer abstractClientPlayer, + float f, float g, + InteractionHand interactionHand, + float h, + ItemStack itemStack, + float i, + PoseStack poseStack, + SubmitNodeCollector submitNodeCollector, + int j, + CallbackInfo ci) { + HandContext.depth++; + HandContext.currentHand = interactionHand; + + if (!ScaleMeConfig.enableHandItemTransform) return; + + boolean offhand = interactionHand == InteractionHand.OFF_HAND; + boolean sep = offhand && ScaleMeConfig.enableSeparateHandTransforms; + + HandContext.tx = sep ? ScaleMeConfig.itemTranslationXOffhand : ScaleMeConfig.itemTranslationX; + HandContext.ty = sep ? ScaleMeConfig.itemTranslationYOffhand : ScaleMeConfig.itemTranslationY; + HandContext.tz = sep ? ScaleMeConfig.itemTranslationZOffhand : ScaleMeConfig.itemTranslationZ; + + HandContext.rx = sep ? ScaleMeConfig.itemRotationXOffhand : ScaleMeConfig.itemRotationX; + HandContext.ry = sep ? ScaleMeConfig.itemRotationYOffhand : ScaleMeConfig.itemRotationY; + HandContext.rz = sep ? ScaleMeConfig.itemRotationZOffhand : ScaleMeConfig.itemRotationZ; + + HandContext.s = sep ? ScaleMeConfig.itemScaleOffhand : ScaleMeConfig.itemScale; + } + + /** Clears {@link HandContext} after rendering completes. */ + @Inject(method = "renderArmWithItem", at = @At("RETURN")) + private void releaseHand( + AbstractClientPlayer abstractClientPlayer, + float f, float g, + InteractionHand interactionHand, + float h, + ItemStack itemStack, + float i, + PoseStack poseStack, + SubmitNodeCollector submitNodeCollector, + int j, + CallbackInfo ci) { + HandContext.depth--; + if (HandContext.depth <= 0) { + HandContext.depth = 0; + HandContext.currentHand = null; + } + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java index b18985a..a8669b5 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java @@ -1,9 +1,12 @@ package com.github.kd_gaming1.scaleme.mixin; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.HandContext; + import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.renderer.SubmitNodeCollector; + import org.joml.Quaternionf; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -11,50 +14,51 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +/** Mixins into {@code LayerRenderState} to apply per-hand item transform overrides. */ @Mixin(targets = "net.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState") public class LayerRenderStateMixin { - @Unique - private boolean scaleme$didPush = false; + @Unique private static final float DEG_TO_RAD = (float) (Math.PI / 180.0); + + @Unique private boolean scaleme$didPush; + @Unique private final Quaternionf scaleme$tmpQuat = new Quaternionf(); + /** Pushes a transformed pose before the item layer is submitted, if any transform is active. */ @Inject(method = "submit", at = @At("HEAD")) - private void onSubmitHead(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { + private void onSubmitHead(PoseStack poseStack, SubmitNodeCollector collector, + int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { if (!ScaleMeConfig.enableHandItemTransform) return; + if (HandContext.depth == 0) return; + + float tx = HandContext.tx, ty = HandContext.ty, tz = HandContext.tz; + float rx = HandContext.rx, ry = HandContext.ry, rz = HandContext.rz; + float s = HandContext.s; + + if (tx == 0f && ty == 0f && tz == 0f && + rx == 0f && ry == 0f && rz == 0f && s == 1f) return; + poseStack.pushPose(); scaleme$didPush = true; + PoseStack.Pose pose = poseStack.last(); - float tx = ScaleMeConfig.itemTranslationX; - float ty = ScaleMeConfig.itemTranslationY; - float tz = ScaleMeConfig.itemTranslationZ; - if (tx != 0f || ty != 0f || tz != 0f) { + if (tx != 0f || ty != 0f || tz != 0f) pose.translate(tx, ty, tz); - } - - float rx = ScaleMeConfig.itemRotationX; - float ry = ScaleMeConfig.itemRotationY; - float rz = ScaleMeConfig.itemRotationZ; - if (rx != 0f || ry != 0f || rz != 0f) { - pose.rotate(new Quaternionf().rotationXYZ( - rx * (float)(Math.PI / 180.0), - ry * (float)(Math.PI / 180.0), - rz * (float)(Math.PI / 180.0) - )); - } - - float s = ScaleMeConfig.itemScale; - if (s != 1f) { + + if (rx != 0f || ry != 0f || rz != 0f) + pose.rotate(scaleme$tmpQuat.rotationXYZ(rx * DEG_TO_RAD, ry * DEG_TO_RAD, rz * DEG_TO_RAD)); + + if (s != 1f) pose.scale(s, s, s); - } } + /** Pops the pose pushed in {@link #onSubmitHead} if one was pushed. */ @Inject(method = "submit", at = @At("RETURN")) - private void onSubmitReturn(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { - if (!ScaleMeConfig.enableHandItemTransform) return; - if (scaleme$didPush) { - poseStack.popPose(); - scaleme$didPush = false; - } + private void onSubmitReturn(PoseStack poseStack, SubmitNodeCollector collector, + int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { + if (!scaleme$didPush) return; + poseStack.popPose(); + scaleme$didPush = false; } -} +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java index 479ee17..aab896a 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java @@ -1,48 +1,58 @@ package com.github.kd_gaming1.scaleme.mixin; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; + import net.minecraft.client.Minecraft; import net.minecraft.world.entity.LivingEntity; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +/** Mixins into {@link LivingEntity} to support swing animation overrides. */ @Mixin(LivingEntity.class) public class LivingEntityMixin { + /** Vanilla default swing duration in ticks. */ @Unique private static final int DEFAULT_SWING_DURATION = 6; + /** + * Modifies the swing duration based on config speed multiplier, + * optionally ignoring potion/enchantment effects. + */ @Inject(method = "getCurrentSwingDuration", at = @At("RETURN"), cancellable = true) private void modifySwingDuration(CallbackInfoReturnable cir) { - if (!ScaleMeConfig.enableAnimOverrides) return; // master toggle - if (!ScaleMeConfig.ignoreSwingSpeedEffects && ScaleMeConfig.swingAnimationSpeed == 1f) return; + if (!ScaleMeConfig.enableAnimOverrides) return; - LivingEntity self = (LivingEntity)(Object)this; - if (!self.level().isClientSide() || self != Minecraft.getInstance().player) return; + boolean ignore = ScaleMeConfig.ignoreSwingSpeedEffects; + float speed = ScaleMeConfig.swingAnimationSpeed; - int duration = cir.getReturnValue(); + if (!ignore && speed == 1f) return; - if (ScaleMeConfig.ignoreSwingSpeedEffects) { - duration = DEFAULT_SWING_DURATION; - } + LivingEntity self = (LivingEntity) (Object) this; + if (!self.level().isClientSide()) return; - if (ScaleMeConfig.swingAnimationSpeed != 1f) { - duration = Math.max(1, Math.round(duration / ScaleMeConfig.swingAnimationSpeed)); - } + var mc = Minecraft.getInstance(); + if (mc.player == null || self != mc.player) return; + + int duration = ignore ? DEFAULT_SWING_DURATION : cir.getReturnValue(); + if (speed != 1f) duration = Math.max(1, Math.round(duration / speed)); cir.setReturnValue(duration); } + /** Suppresses the attack animation for the local player when configured. */ @Inject(method = "getAttackAnim", at = @At("RETURN"), cancellable = true) private void suppressAttackAnim(float partialTick, CallbackInfoReturnable cir) { - if (!ScaleMeConfig.enableAnimOverrides) return; // master toggle + if (!ScaleMeConfig.enableAnimOverrides) return; if (!ScaleMeConfig.disableSwingAnimation) return; - LivingEntity self = (LivingEntity)(Object)this; - if (self.level().isClientSide() && self == Minecraft.getInstance().player) { + LivingEntity self = (LivingEntity) (Object) this; + var mc = Minecraft.getInstance(); + if (self.level().isClientSide() && self == mc.player) { cir.setReturnValue(0f); } } diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java new file mode 100644 index 0000000..5d10d35 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java @@ -0,0 +1,23 @@ +package com.github.kd_gaming1.scaleme.util; + +import net.minecraft.world.InteractionHand; + +/** + * Shared context for the current first-person hand render pass. + * Populated by {@code ItemInHandRendererMixin} before each item render and cleared after. + */ +public final class HandContext { + + /** {@code null} when outside a first-person hand render. */ + public static InteractionHand currentHand = null; + + /** Nesting depth of active hand render passes. */ + public static int depth = 0; + + /** Active transform values for the current hand render. */ + public static float tx, ty, tz; + public static float rx, ry, rz; + public static float s = 1f; + + private HandContext() {} +} \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index f106be5..8610280 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -2,17 +2,22 @@ "scaleme.midnightconfig.title": "ScaleMe", "scaleme.midnightconfig.category.hand_item": "Held Item", + "scaleme.midnightconfig.category.animation": "Item Animation", "scaleme.midnightconfig.category.view": "Camera & Crosshair", - "scaleme.midnightconfig.category.scaling": "Scaling", "scaleme.midnightconfig.handDesc": "Adjust how your held item looks in your hand", "scaleme.midnightconfig.spacer1": "", "scaleme.midnightconfig.spacer2": "", + "scaleme.midnightconfig.spacer3": "", "scaleme.midnightconfig.spacer4": "", + "scaleme.midnightconfig.spacer5": "", "scaleme.midnightconfig.enableHandItemTransform": "Enable Custom Item Transform", "scaleme.midnightconfig.enableHandItemTransform.tooltip": "Turns on the custom held item positioning. Disable this to restore the default look and avoid conflicts with other mods.", + "scaleme.midnightconfig.enableSeparateHandTransforms": "Enable Separate Off-Hand Transform \n(Off-Hand sliders at the bottom of the page)", + "scaleme.midnightconfig.enableSeparateHandTransforms.tooltip": "Turns on separate positioning for your off-hand item. When disabled, the off-hand uses the same transform as the main hand. Enable this to customise the settings below.", + "scaleme.midnightconfig.itemScale": "Item Size", "scaleme.midnightconfig.itemScale.tooltip": "How big or small the item in your hand appears.\nSmaller values make it tiny, larger values make it huge.", @@ -34,6 +39,29 @@ "scaleme.midnightconfig.itemRotationZ": "Spin Item Clockwise / Counterclockwise", "scaleme.midnightconfig.itemRotationZ.tooltip": "Spins the item in your hand clockwise or counterclockwise.", + "scaleme.midnightconfig.offhandDesc": "Off-Hand Item — requires 'Enable Separate Off-Hand Transform' to be ON", + + "scaleme.midnightconfig.itemScaleOffhand": "Off-Hand Item Size", + "scaleme.midnightconfig.itemScaleOffhand.tooltip": "How big or small the off-hand item appears.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemTranslationXOffhand": "Move Off-Hand Item Left / Right", + "scaleme.midnightconfig.itemTranslationXOffhand.tooltip": "Shifts the off-hand item to the left or right.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemTranslationYOffhand": "Move Off-Hand Item Up / Down", + "scaleme.midnightconfig.itemTranslationYOffhand.tooltip": "Shifts the off-hand item up or down.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemTranslationZOffhand": "Move Off-Hand Item Closer / Further", + "scaleme.midnightconfig.itemTranslationZOffhand.tooltip": "Shifts the off-hand item closer to or further from your screen.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemRotationXOffhand": "Tilt Off-Hand Item Up / Down", + "scaleme.midnightconfig.itemRotationXOffhand.tooltip": "Tilts the off-hand item up or down.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemRotationYOffhand": "Rotate Off-Hand Item Left / Right", + "scaleme.midnightconfig.itemRotationYOffhand.tooltip": "Rotates the off-hand item to the left or right.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + + "scaleme.midnightconfig.itemRotationZOffhand": "Spin Off-Hand Item Clockwise / Counterclockwise", + "scaleme.midnightconfig.itemRotationZOffhand.tooltip": "Spins the off-hand item clockwise or counterclockwise.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.animDesc": "Adjust how your held item animates", "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes", From 8da6b59514ff3e4f8c5c03f6f72e7054988219ee Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:52:36 +0100 Subject: [PATCH 04/16] feat: add support for showing own nametag in third person and skip front view in selfie cam --- CHANGELOG.md | 6 +++- .../github/kd_gaming1/scaleme/ScaleMe.java | 10 ++++++ .../scaleme/config/ScaleMeConfig.java | 3 ++ .../scaleme/mixin/AvatarRendererMixin.java | 36 +++++++++++++++++++ .../scaleme/mixin/CameraTypeMixin.java | 30 ++++++++++++++++ .../kd_gaming1/scaleme/mixin/MixinGui.java | 29 +++++++++++++++ src/main/resources/scaleme.mixins.json | 5 ++- 7 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/CameraTypeMixin.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/MixinGui.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 016f523..e85b93a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,5 @@ -- chore: updated to 1.21.11 \ No newline at end of file +Rewrite and new features: + +- Added showOwnNametagInThirdPerson mode +- Optimized performance for Item Scaling +- Added support for separate scaling between main hand and off hand \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index ff65525..3ed608f 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -2,17 +2,27 @@ import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; import eu.midnightdust.lib.config.MidnightConfig; +import net.azureaaron.hmapi.events.HypixelPacketEvents; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.atomic.AtomicBoolean; + public class ScaleMe implements ClientModInitializer { public static final String MOD_ID = "scaleme"; public static final Logger LOGGER = LoggerFactory.getLogger("Scale Me"); + public static final AtomicBoolean hypixelPacketReceived = new AtomicBoolean(false); + @Override public void onInitializeClient() { MidnightConfig.init(MOD_ID, ScaleMeConfig.class); + + HypixelPacketEvents.HELLO.register((packet) -> hypixelPacketReceived.set(true)); + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> hypixelPacketReceived.set(false)); + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 3e35c91..1b15d70 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -114,4 +114,7 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = VIEW) public static boolean disableSelfieCam = false; + + @Entry(category = VIEW) + public static boolean showOwnNametagInThirdPerson = false; } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java new file mode 100644 index 0000000..810ded3 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java @@ -0,0 +1,36 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import net.minecraft.client.CameraType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.entity.player.AvatarRenderer; +import net.minecraft.world.entity.Avatar; +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.CallbackInfoReturnable; + +@Mixin(AvatarRenderer.class) +public class AvatarRendererMixin { + + @Inject( + method = "shouldShowName(Lnet/minecraft/world/entity/Avatar;D)Z", + at = @At("HEAD"), + cancellable = true + ) + private void scaleme$showOwnNametagInThirdPerson(Avatar entity, double distanceToCameraSq, CallbackInfoReturnable cir) { + if (!ScaleMeConfig.showOwnNametagInThirdPerson) return; + + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null) return; + + // Only your own nametag + if (entity != mc.player) return; + + // Only in third person + CameraType cam = mc.options.getCameraType(); + if (cam.isFirstPerson()) return; + + cir.setReturnValue(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/CameraTypeMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/CameraTypeMixin.java new file mode 100644 index 0000000..111975a --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/CameraTypeMixin.java @@ -0,0 +1,30 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import net.minecraft.client.CameraType; +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.CallbackInfoReturnable; + +/** + * Skips THIRD_PERSON_FRONT when selfie cam is disabled. + *

+ * Normal: FIRST_PERSON -> THIRD_PERSON_BACK -> THIRD_PERSON_FRONT -> FIRST_PERSON + * New: FIRST_PERSON -> THIRD_PERSON_BACK -> FIRST_PERSON + */ +@Mixin(CameraType.class) +public class CameraTypeMixin { + + @Inject(method = "cycle", at = @At("HEAD"), cancellable = true) + private void scaleme$skipFrontViewIfDisabled(CallbackInfoReturnable cir) { + if (!ScaleMeConfig.disableSelfieCam) return; + + CameraType current = (CameraType) (Object) this; + + // If we are about to go BACK -> FRONT, go BACK -> FIRST instead + if (current == CameraType.THIRD_PERSON_BACK) { + cir.setReturnValue(CameraType.FIRST_PERSON); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/MixinGui.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/MixinGui.java new file mode 100644 index 0000000..74610c8 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/MixinGui.java @@ -0,0 +1,29 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.CameraType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Gui.class) +public class MixinGui { + + @ModifyExpressionValue( + method = "renderCrosshair(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/CameraType;isFirstPerson()Z") + ) + private boolean scaleme$shouldRenderCrosshair(boolean original) { + if (original) return true; + + Minecraft mc = Minecraft.getInstance(); + CameraType cameraType = mc.options.getCameraType(); + + if (cameraType == CameraType.THIRD_PERSON_FRONT) { + return ScaleMeConfig.enableCrosshairInThirdPersonFront; + } + return ScaleMeConfig.enableCrosshairInThirdPerson; + } +} \ No newline at end of file diff --git a/src/main/resources/scaleme.mixins.json b/src/main/resources/scaleme.mixins.json index 6a05179..95c1369 100644 --- a/src/main/resources/scaleme.mixins.json +++ b/src/main/resources/scaleme.mixins.json @@ -8,8 +8,11 @@ "defaultRequire": 1 }, "client": [ + "AvatarRendererMixin", + "CameraTypeMixin", "ItemInHandRendererMixin", "LayerRenderStateMixin", - "LivingEntityMixin" + "LivingEntityMixin", + "MixinGui" ] } \ No newline at end of file From a6d28f2c01293e684c2ebd512661bbed6cce74d8 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:53:01 +0100 Subject: [PATCH 05/16] feat: add option to show own nametag in third person and update related tooltips --- CHANGELOG.md | 2 +- src/main/resources/assets/scaleme/lang/en_us.json | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e85b93a..2cbdbd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ Rewrite and new features: -- Added showOwnNametagInThirdPerson mode +- Added Show Own Nametag in Third Person mode - Optimized performance for Item Scaling - Added support for separate scaling between main hand and off hand \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 8610280..e8b5026 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -88,5 +88,8 @@ "scaleme.midnightconfig.enableCrosshairInThirdPersonFront.tooltip": "Shows the crosshair when the camera is facing your character from the front.", "scaleme.midnightconfig.disableSelfieCam": "Disable Front Camera View", - "scaleme.midnightconfig.disableSelfieCam.tooltip": "Removes the option to flip the camera to face your character." + "scaleme.midnightconfig.disableSelfieCam.tooltip": "Removes the option to flip the camera to face your character.", + + "scaleme.midnightconfig.showOwnNametagInThirdPerson": "Show Own Nametag in Third Person", + "scaleme.midnightconfig.showOwnNametagInThirdPerson.tooltip": "Shows your own nametag when you are in third person." } \ No newline at end of file From 43b6c8c0cbc6fa942326e2c7f946726916468f49 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:32:53 +0100 Subject: [PATCH 06/16] feat: implement entity scaling and name tag adjustments for Hypixel integration --- .../github/kd_gaming1/scaleme/ScaleMe.java | 23 ++++++++++- .../scaleme/config/ScaleMeConfig.java | 21 ++++++++++ .../scaleme/mixin/AvatarRendererMixin.java | 31 ++++++++++++++ .../mixin/LivingEntityRendererMixin.java | 28 +++++++++++++ .../scaleme/util/HypixelLocationState.java | 37 +++++++++++++++++ .../scaleme/util/HypixelNpcUtil.java | 32 +++++++++++++++ .../kd_gaming1/scaleme/util/PerTickCache.java | 39 ++++++++++++++++++ .../scaleme/util/ScaleResolver.java | 40 +++++++++++++++++++ .../resources/assets/scaleme/lang/en_us.json | 18 +++++++++ src/main/resources/scaleme.mixins.json | 1 + 10 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityRendererMixin.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/PerTickCache.java create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 3ed608f..6548ab3 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -1,11 +1,20 @@ package com.github.kd_gaming1.scaleme; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.HypixelLocationState; import eu.midnightdust.lib.config.MidnightConfig; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.azureaaron.hmapi.events.HypixelPacketEvents; +import net.azureaaron.hmapi.network.HypixelNetworking; +import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +//? if >=1.21.11 { +/*import net.minecraft.util.Util; +*///?} else { +import net.minecraft.Util; +//?} import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +30,18 @@ public class ScaleMe implements ClientModInitializer { public void onInitializeClient() { MidnightConfig.init(MOD_ID, ScaleMeConfig.class); - HypixelPacketEvents.HELLO.register((packet) -> hypixelPacketReceived.set(true)); - ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> hypixelPacketReceived.set(false)); + HypixelLocationState.register(); + + HypixelPacketEvents.HELLO.register((packet) -> { + HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> { + map.put(LocationUpdateS2CPacket.ID, 1); + })); + hypixelPacketReceived.set(true); + }); + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { + hypixelPacketReceived.set(false); + HypixelLocationState.reset(); + }); } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 1b15d70..956aefc 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -6,6 +6,7 @@ public class ScaleMeConfig extends MidnightConfig { public static final String HAND = "hand_item"; public static final String ANIM = "animation"; + public static final String SCALE = "scale"; public static final String VIEW = "view"; // ── Hand Item Transform ───────────────────────────────────────────────── @@ -101,6 +102,26 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = ANIM) public static boolean disableSwingAnimation = false; + // ── Scale ─────────────────────────────────────────────────────────────── + + @Comment(category = SCALE) + public static Comment scaleDesc; + + @Entry(category = SCALE) + public static boolean scaleNameTags = false; + + @Entry(category = SCALE, isSlider = true, min = 0.1f, max = 4f) + public static float playerScale = 1f; + + @Entry(category = SCALE, isSlider = true, min = 0.1f, max = 4f) + public static float otherPlayersScale = 1f; + + @Entry(category = SCALE, isSlider = true, min = 0.1f, max = 4f) + public static float villagerNpcScale = 1f; + + @Entry(category = SCALE, isSlider = true, min = 0.1f, max = 4f) + public static float hypixelNpcScale = 1f; + // ── View ──────────────────────────────────────────────────────────────── @Comment(category = VIEW) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java index 810ded3..f3fa501 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java @@ -1,13 +1,19 @@ package com.github.kd_gaming1.scaleme.mixin; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.PerTickCache; +import com.github.kd_gaming1.scaleme.util.ScaleResolver; +import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.CameraType; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.player.AvatarRenderer; +import net.minecraft.client.renderer.entity.state.AvatarRenderState; import net.minecraft.world.entity.Avatar; +import net.minecraft.world.phys.Vec3; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(AvatarRenderer.class) @@ -33,4 +39,29 @@ public class AvatarRendererMixin { cir.setReturnValue(true); } + + @Inject(method = "scale*", at = @At("HEAD"), cancellable = true) + private void onScale(AvatarRenderState state, PoseStack poseStack, CallbackInfo ci) { + if (ScaleResolver.noScalingConfigured()) return; + + float scale = PerTickCache.getScale(state.id); + if (scale == 1f) return; + + float s = 0.9375F * scale; + poseStack.scale(s, s, s); + ci.cancel(); + } + + @Inject(method = "extractRenderState*", at = @At("TAIL")) + private void scaleme$adjustNameTagAttachment(Avatar entity, AvatarRenderState state, float partialTicks, CallbackInfo ci) { + if (ScaleResolver.noScalingConfigured()) return; + if (!ScaleMeConfig.scaleNameTags) return; + if (state.nameTagAttachment == null) return; + + float scale = PerTickCache.getScale(state.id); + if (scale == 1f) return; + + double y = state.nameTagAttachment.y; + state.nameTagAttachment = new Vec3(state.nameTagAttachment.x, y * scale, state.nameTagAttachment.z); + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityRendererMixin.java new file mode 100644 index 0000000..1612f4c --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityRendererMixin.java @@ -0,0 +1,28 @@ +package com.github.kd_gaming1.scaleme.mixin; + +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import net.minecraft.client.renderer.entity.LivingEntityRenderer; +import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; +import net.minecraft.world.entity.LivingEntity; +//? if >=1.21.11 { +/*import net.minecraft.world.entity.npc.villager.Villager; +*///?} else { +import net.minecraft.world.entity.npc.Villager; + //?} +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(LivingEntityRenderer.class) +public class LivingEntityRendererMixin { + + @Inject(method = "extractRenderState*", at = @At("TAIL")) + private void scaleme$villagerScale(T entity, S state, float partialTicks, CallbackInfo ci) { + if (ScaleMeConfig.villagerNpcScale == 1f) return; + if (!(entity instanceof Villager)) return; + + // Override the scale baked into render state + state.scale = entity.getScale() * ScaleMeConfig.villagerNpcScale; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java new file mode 100644 index 0000000..506a179 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java @@ -0,0 +1,37 @@ +package com.github.kd_gaming1.scaleme.util; + +import net.azureaaron.hmapi.events.HypixelPacketEvents; +import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; + +public class HypixelLocationState { + + private static boolean onSkyblock = false; + private static boolean inDungeon = false; + + private HypixelLocationState() {} + + public static void register() { + HypixelPacketEvents.LOCATION_UPDATE.register(packet -> { + if (packet instanceof LocationUpdateS2CPacket location) { + onSkyblock = location.serverType() + .map(type -> type.equals("SKYBLOCK")) + .orElse(false); + + inDungeon = onSkyblock && location.map() + .map(map -> map.equals("Dungeon")) + .orElse(false); + } + }); + } + + /** True if the player is on Hypixel SkyBlock (any island/mode). */ + public static boolean isOnSkyblock() { return onSkyblock; } + + /** True if the player is in a SkyBlock Dungeon specifically. */ + public static boolean isInDungeon() { return inDungeon; } + + public static void reset() { + onSkyblock = false; + inDungeon = false; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java new file mode 100644 index 0000000..dee5bd3 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java @@ -0,0 +1,32 @@ +package com.github.kd_gaming1.scaleme.util; + +import com.github.kd_gaming1.scaleme.ScaleMe; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Team.Visibility; + +/** + * Utility for detecting Hypixel NPCs. + *

+ * Detection method: Hypixel NPCs are fake player entities whose scoreboard team + * has name tag visibility set to {@link Visibility#NEVER} to hide their tags. + * Gated on {@code hypixelPacketReceived} so this is a no-op on other servers. + */ +public class HypixelNpcUtil { + + private HypixelNpcUtil() {} + + /** + * Returns true if the entity is a Hypixel NPC. + * Always returns false if not connected to Hypixel. + */ + public static boolean isHypixelNpc(AbstractClientPlayer player) { + if (!ScaleMe.hypixelPacketReceived.get()) return false; + if (player == null) return false; + + PlayerTeam team = player.getTeam(); + if (team == null) return false; + + return team.getNameTagVisibility() == Visibility.NEVER; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/PerTickCache.java b/src/main/java/com/github/kd_gaming1/scaleme/util/PerTickCache.java new file mode 100644 index 0000000..45f0a49 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/PerTickCache.java @@ -0,0 +1,39 @@ +package com.github.kd_gaming1.scaleme.util; + +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import net.minecraft.client.Minecraft; + +public final class PerTickCache { + + private PerTickCache() {} + + private static final Int2FloatOpenHashMap cache = new Int2FloatOpenHashMap(); + private static long lastTick = Long.MIN_VALUE; + private static Object lastLevel = null; + + static { + cache.defaultReturnValue(Float.NaN); + } + + /** Returns the cached scale for entityId, computing it once per tick. */ + public static float getScale(int entityId) { + Minecraft mc = Minecraft.getInstance(); + if (mc.level == null) return 1f; + + Object level = mc.level; + long tick = mc.level.getGameTime(); + + if (level != lastLevel || tick != lastTick) { + cache.clear(); + lastLevel = level; + lastTick = tick; + } + + float cached = cache.get(entityId); + if (!Float.isNaN(cached)) return cached; + + float scale = ScaleResolver.resolveScale(mc, entityId); + cache.put(entityId, scale); + return scale; + } +} diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java new file mode 100644 index 0000000..fd70227 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java @@ -0,0 +1,40 @@ +package com.github.kd_gaming1.scaleme.util; + +import com.github.kd_gaming1.scaleme.ScaleMe; +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; + +public final class ScaleResolver { + + private ScaleResolver() {} + + /** Returns the configured scale for the given entity id, or 1.0f if no scaling applies. */ + public static float resolveScale(Minecraft mc, int entityId) { + if (mc.player == null || mc.level == null) return 1f; + + boolean onHypixel = ScaleMe.hypixelPacketReceived.get(); + boolean scalingAllowed = !onHypixel || HypixelLocationState.isOnSkyblock(); + + if (entityId == mc.player.getId()) { + return scalingAllowed ? ScaleMeConfig.playerScale : 1f; + } + + if (onHypixel && ScaleMeConfig.hypixelNpcScale != 1f) { + var entity = mc.level.getEntity(entityId); + if (entity instanceof AbstractClientPlayer player && HypixelNpcUtil.isHypixelNpc(player)) { + return HypixelLocationState.isInDungeon() ? 1f : ScaleMeConfig.hypixelNpcScale; + } + } + + return scalingAllowed ? ScaleMeConfig.otherPlayersScale : 1f; + } + + /** True when at least one scale config differs from 1.0f. */ + public static boolean noScalingConfigured() { + return ScaleMeConfig.playerScale == 1f + && ScaleMeConfig.otherPlayersScale == 1f + && ScaleMeConfig.hypixelNpcScale == 1f + && ScaleMeConfig.villagerNpcScale == 1f; + } +} diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index e8b5026..970ff08 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -3,6 +3,7 @@ "scaleme.midnightconfig.category.hand_item": "Held Item", "scaleme.midnightconfig.category.animation": "Item Animation", + "scaleme.midnightconfig.category.scale": "Entity Scale", "scaleme.midnightconfig.category.view": "Camera & Crosshair", "scaleme.midnightconfig.handDesc": "Adjust how your held item looks in your hand", @@ -79,6 +80,23 @@ "scaleme.midnightconfig.disableSwingAnimation": "Disable Swing Animation", "scaleme.midnightconfig.disableSwingAnimation.tooltip": "Completely removes the arm swing animation. Your arm stays still when you click.", + "scaleme.midnightconfig.scaleDesc": "Adjust the size of players and entities in the world", + + "scaleme.midnightconfig.scaleNameTags": "Let name tags follow the player's scale", + "scaleme.midnightconfig.scaleNameTags.tooltip": "Makes name tags follow the player's scale instead of being fixed at 1.0x.\nThis only affects players and entities that have their own name tag.", + + "scaleme.midnightconfig.playerScale": "Your Player Scale (1.0 = Disabled)", + "scaleme.midnightconfig.playerScale.tooltip": "Changes how big or small your own character appears to others.\nDoes not affect your own first-person view.", + + "scaleme.midnightconfig.otherPlayersScale": "Other Players Scale (1.0 = Disabled)", + "scaleme.midnightconfig.otherPlayersScale.tooltip": "Changes how big or small other players appear to you.", + + "scaleme.midnightconfig.villagerNpcScale": "Villager NPC Scale (1.0 = Disabled)", + "scaleme.midnightconfig.villagerNpcScale.tooltip": "Changes how big or small villagers NPC in Hypixel appear.", + + "scaleme.midnightconfig.hypixelNpcScale": "Hypixel NPC Scale (1.0 = Disabled)", + "scaleme.midnightconfig.hypixelNpcScale.tooltip": "Changes how big or small Hypixel NPCs appear.\nOnly has an effect when playing on Hypixel.", + "scaleme.midnightconfig.viewDescription": "Control your crosshair and camera behaviour", "scaleme.midnightconfig.enableCrosshairInThirdPerson": "Show Crosshair Behind You", diff --git a/src/main/resources/scaleme.mixins.json b/src/main/resources/scaleme.mixins.json index 95c1369..8e009bf 100644 --- a/src/main/resources/scaleme.mixins.json +++ b/src/main/resources/scaleme.mixins.json @@ -13,6 +13,7 @@ "ItemInHandRendererMixin", "LayerRenderStateMixin", "LivingEntityMixin", + "LivingEntityRendererMixin", "MixinGui" ] } \ No newline at end of file From 053b5c8481e0da419b2f3fef3781267ef4e64003 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:41:38 +0100 Subject: [PATCH 07/16] feat: improved Mixin logic --- .../kd_gaming1/scaleme/mixin/AvatarRendererMixin.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java index f3fa501..047f7e3 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/AvatarRendererMixin.java @@ -40,16 +40,14 @@ public class AvatarRendererMixin { cir.setReturnValue(true); } - @Inject(method = "scale*", at = @At("HEAD"), cancellable = true) + @Inject(method = "scale*", at = @At("TAIL")) private void onScale(AvatarRenderState state, PoseStack poseStack, CallbackInfo ci) { if (ScaleResolver.noScalingConfigured()) return; float scale = PerTickCache.getScale(state.id); if (scale == 1f) return; - float s = 0.9375F * scale; - poseStack.scale(s, s, s); - ci.cancel(); + poseStack.scale(scale, scale, scale); } @Inject(method = "extractRenderState*", at = @At("TAIL")) From 7debc265ac63484686124ea145553f4e203e6c3a Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Sun, 22 Feb 2026 21:38:22 +0100 Subject: [PATCH 08/16] feat: add command to open configuration menu --- .../github/kd_gaming1/scaleme/ScaleMe.java | 4 ++ .../kd_gaming1/scaleme/command/Commands.java | 60 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/command/Commands.java diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 6548ab3..91fe140 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -1,5 +1,6 @@ package com.github.kd_gaming1.scaleme; +import com.github.kd_gaming1.scaleme.command.Commands; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; import com.github.kd_gaming1.scaleme.util.HypixelLocationState; import eu.midnightdust.lib.config.MidnightConfig; @@ -18,6 +19,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.xml.stream.events.Comment; import java.util.concurrent.atomic.AtomicBoolean; public class ScaleMe implements ClientModInitializer { @@ -43,5 +45,7 @@ public void onInitializeClient() { HypixelLocationState.reset(); }); + // Register commands + Commands.register(); } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/command/Commands.java b/src/main/java/com/github/kd_gaming1/scaleme/command/Commands.java new file mode 100644 index 0000000..23e000f --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/command/Commands.java @@ -0,0 +1,60 @@ +package com.github.kd_gaming1.scaleme.command; + +import com.github.kd_gaming1.scaleme.ScaleMe; +import com.mojang.brigadier.context.CommandContext; +import eu.midnightdust.lib.config.MidnightConfig; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class Commands { + public static void register() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(literal("scaleme") + .executes(Commands::executeOpenConfig) + .then(literal("config") + .executes(Commands::executeOpenConfig)) + )); + } + + /** + * Opens the configuration menu. + * Uses client.send() to delay opening until after the chat closes. + */ + private static int executeOpenConfig(CommandContext ctx) { + Minecraft client = Minecraft.getInstance(); + + if (client.player == null) { + sendError(ctx); + return 0; + } + + // Schedule GUI opening on the next tick (after chat closes) + client.schedule(() -> { + try { + client.setScreen(MidnightConfig.getScreen(client.screen, ScaleMe.MOD_ID)); + } catch (Exception e) { + ScaleMe.LOGGER.error("Failed to open config menu", e); + } + }); + + sendSuccess(ctx); + return 1; + } + + /** + * Sends a success message to the player. + */ + private static void sendSuccess(CommandContext ctx) { + ctx.getSource().sendFeedback(Component.literal("§a[Scale Me] " + "Opening configuration menu...")); + } + + /** + * Sends an error message to the player. + */ + private static void sendError(CommandContext ctx) { + ctx.getSource().sendError(Component.literal("§c[Scale Me] " + "You must be in-game to open the config menu.")); + } +} From 4f55f2832356e2539d2f1183ea570f3b9b52302e Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:58:33 +0100 Subject: [PATCH 09/16] feat: add customizable swing animation settings and hand transform overrides --- .../scaleme/config/ScaleMeConfig.java | 48 ++++-- .../mixin/ItemInHandRendererMixin.java | 147 ++++++++++++------ .../scaleme/mixin/LayerRenderStateMixin.java | 50 +++--- .../kd_gaming1/scaleme/util/HandContext.java | 50 ++++-- .../scaleme/util/HypixelLocationState.java | 30 ++-- .../resources/assets/scaleme/lang/en_us.json | 30 ++++ 6 files changed, 245 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 956aefc..dbe4e26 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -26,13 +26,13 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND, name = "Scale", isSlider = true, min = 0.1f, max = 4f, precision = 1000) public static float itemScale = 1f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationX = 0f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationY = 0f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationZ = 0f; @Comment(category = HAND, centered = true) @@ -61,18 +61,15 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND, isSlider = true, min = 0.1f, max = 4f, precision = 1000) public static float itemScaleOffhand = 1f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationXOffhand = 0f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationYOffhand = 0f; - @Entry(category = HAND, isSlider = true, min = -1.5f, max = 1.5f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationZOffhand = 0f; - @Comment(category = HAND, centered = true) - public static Comment spacer5; - @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationXOffhand = 0f; @@ -102,6 +99,39 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = ANIM) public static boolean disableSwingAnimation = false; + @Comment(category = ANIM, centered = true) + public static Comment spacer5; + + @Entry(category = ANIM) + public static boolean enableSwingOverride = false; + + @Entry(category = ANIM, isSlider = true, min = -2f, max = 2f) + public static float swingArmXScale = -0.4f; // vanilla: -0.4 + + @Entry(category = ANIM, isSlider = true, min = -2f, max = 2f) + public static float swingArmYScale = 0.2f; // vanilla: 0.2 + + @Entry(category = ANIM, isSlider = true, min = -2f, max = 2f) + public static float swingArmZScale = -0.2f; // vanilla: -0.2 + + @Entry(category = ANIM) + public static boolean swingArmXMultiplyBySide = true; + + @Entry(category = ANIM, isSlider = true, min = 0f, max = 90f, precision = 10) + public static float swingPreRotationY = 45f; // vanilla: 45 + + @Entry(category = ANIM, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float swingArcYAmount = -20f; // vanilla: -20 + + @Entry(category = ANIM, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float swingArcZAmount = -20f; // vanilla: -20 + + @Entry(category = ANIM, isSlider = true, min = -180f, max = 180f, precision = 10) + public static float swingArcXAmount = -80f; // vanilla: -80 + + @Entry(category = ANIM) + public static boolean swingCounterRotation = true; + // ── Scale ─────────────────────────────────────────────────────────────── @Comment(category = SCALE) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java index 6c851e1..b6a3697 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -5,26 +5,34 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; +import net.minecraft.util.Mth; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -/** Mixins into {@link ItemInHandRenderer} to support animation and hand context overrides. */ +/** Hooks into {@link ItemInHandRenderer} for swing and hand-context overrides. */ @Mixin(ItemInHandRenderer.class) public class ItemInHandRendererMixin { - /** - * Overrides the attack strength scale used during the swing animation tick. - * When swing bobbing is disabled, returns 1.0 to suppress the effect. - */ + private static final float PI = (float) Math.PI; + + @Shadow private void applyItemArmTransform(PoseStack poseStack, HumanoidArm arm, float inverseArmHeight) {} + @Shadow private void applyItemArmAttackTransform(PoseStack poseStack, HumanoidArm arm, float attackProgress) {} + + // ── Swing Bobbing ─────────────────────────────────────────────────────── + + /** Forces attack-strength scale to 1.0 so the item never dips on attack. */ @ModifyExpressionValue( method = "tick()V", at = @At( @@ -36,62 +44,105 @@ public class ItemInHandRendererMixin { //?} ) ) - private float scaleme$disableSwingBobbing_attackStrength(float original) { - if (!ScaleMeConfig.enableAnimOverrides) return original; - if (!ScaleMeConfig.disableSwingBobbing) return original; - - return 1.0F; + private float scaleme$suppressSwingBobbing(float originalScale) { + if (!ScaleMeConfig.enableAnimOverrides || !ScaleMeConfig.disableSwingBobbing) return originalScale; + return 1.0f; } - /** Populates {@link HandContext} with the current hand and its transform values before rendering. */ + // ── Hand Context ──────────────────────────────────────────────────────── + @Inject(method = "renderArmWithItem", at = @At("HEAD")) - private void captureHand( - AbstractClientPlayer abstractClientPlayer, - float f, float g, - InteractionHand interactionHand, - float h, - ItemStack itemStack, - float i, + private void scaleme$captureHand( + AbstractClientPlayer player, + float tickDelta, float pitch, + InteractionHand hand, + float swingProgress, + ItemStack heldItem, + float equipProgress, PoseStack poseStack, - SubmitNodeCollector submitNodeCollector, - int j, + SubmitNodeCollector collector, + int packedLight, CallbackInfo ci) { - HandContext.depth++; - HandContext.currentHand = interactionHand; - - if (!ScaleMeConfig.enableHandItemTransform) return; - - boolean offhand = interactionHand == InteractionHand.OFF_HAND; - boolean sep = offhand && ScaleMeConfig.enableSeparateHandTransforms; - HandContext.tx = sep ? ScaleMeConfig.itemTranslationXOffhand : ScaleMeConfig.itemTranslationX; - HandContext.ty = sep ? ScaleMeConfig.itemTranslationYOffhand : ScaleMeConfig.itemTranslationY; - HandContext.tz = sep ? ScaleMeConfig.itemTranslationZOffhand : ScaleMeConfig.itemTranslationZ; + HandContext.renderDepth++; - HandContext.rx = sep ? ScaleMeConfig.itemRotationXOffhand : ScaleMeConfig.itemRotationX; - HandContext.ry = sep ? ScaleMeConfig.itemRotationYOffhand : ScaleMeConfig.itemRotationY; - HandContext.rz = sep ? ScaleMeConfig.itemRotationZOffhand : ScaleMeConfig.itemRotationZ; - - HandContext.s = sep ? ScaleMeConfig.itemScaleOffhand : ScaleMeConfig.itemScale; + if (ScaleMeConfig.enableHandItemTransform) { + HandContext.update(hand); + } else { + HandContext.currentHand = hand; + } } - /** Clears {@link HandContext} after rendering completes. */ @Inject(method = "renderArmWithItem", at = @At("RETURN")) - private void releaseHand( - AbstractClientPlayer abstractClientPlayer, - float f, float g, - InteractionHand interactionHand, - float h, - ItemStack itemStack, - float i, + private void scaleme$releaseHand( + AbstractClientPlayer player, + float tickDelta, float pitch, + InteractionHand hand, + float swingProgress, + ItemStack heldItem, + float equipProgress, PoseStack poseStack, - SubmitNodeCollector submitNodeCollector, - int j, + SubmitNodeCollector collector, + int packedLight, CallbackInfo ci) { - HandContext.depth--; - if (HandContext.depth <= 0) { - HandContext.depth = 0; + + HandContext.renderDepth--; + if (HandContext.renderDepth <= 0) { + HandContext.renderDepth = 0; HandContext.currentHand = null; } } + + // ── Swing Drift Override ──────────────────────────────────────────────── + + /** Replaces vanilla's fixed swing-drift translation with per-axis configurable amounts. + * Vanilla defaults: X = -0.4, Y = 0.2, Z = -0.2 */ + //? if >=1.21.11 { + /*@Inject(method = "swingArm", at = @At("HEAD"), cancellable = true) + private void scaleme$overrideSwingDrift(float attackProgress, PoseStack poseStack, int handSide, HumanoidArm arm, CallbackInfo ci) { + *///?} else { + @Inject(method = "swingArm", at = @At("HEAD"), cancellable = true) + private void scaleme$overrideSwingDrift(float attackProgress, float inverseArmHeight, PoseStack poseStack, int handSide, HumanoidArm arm, CallbackInfo ci) { + //?} + + if (!ScaleMeConfig.enableAnimOverrides || !ScaleMeConfig.enableSwingOverride) return; + ci.cancel(); + + float sqrtAttack = Mth.sqrt(attackProgress); + + float driftX = ScaleMeConfig.swingArmXScale * Mth.sin(sqrtAttack * PI); + float driftY = ScaleMeConfig.swingArmYScale * Mth.sin(sqrtAttack * (PI * 2)); + float driftZ = ScaleMeConfig.swingArmZScale * Mth.sin(attackProgress * PI); + + poseStack.translate(ScaleMeConfig.swingArmXMultiplyBySide ? handSide * driftX : driftX, driftY, driftZ); + + //? if =1.21.10 { + applyItemArmTransform(poseStack, arm, inverseArmHeight); + //?} + applyItemArmAttackTransform(poseStack, arm, attackProgress); + } + + // ── Swing Arc Override ────────────────────────────────────────────────── + + /** Replaces vanilla's fixed swing-arc rotations with per-axis configurable amounts. + * Vanilla defaults: preRotation Y = 45°, arc Y = -20°, Z = -20°, X chop = -80° */ + @Inject(method = "applyItemArmAttackTransform", at = @At("HEAD"), cancellable = true) + private void scaleme$overrideSwingArc(PoseStack poseStack, HumanoidArm arm, float attackProgress, CallbackInfo ci) { + if (!ScaleMeConfig.enableAnimOverrides || !ScaleMeConfig.enableSwingOverride) return; + ci.cancel(); + + int armSideSign = (arm == HumanoidArm.RIGHT) ? 1 : -1; + + float lateSwingCurve = Mth.sin(attackProgress * attackProgress * PI); // peaks late, drives Y arc + float midSwingCurve = Mth.sin(Mth.sqrt(attackProgress) * PI); // peaks mid, drives Z tilt + X chop + + poseStack.mulPose(Axis.YP.rotationDegrees(armSideSign * (ScaleMeConfig.swingPreRotationY + lateSwingCurve * ScaleMeConfig.swingArcYAmount))); + poseStack.mulPose(Axis.ZP.rotationDegrees(armSideSign * midSwingCurve * ScaleMeConfig.swingArcZAmount)); + poseStack.mulPose(Axis.XP.rotationDegrees(midSwingCurve * ScaleMeConfig.swingArcXAmount)); + + // Undoes the Y pre-rotation so the item swings back to center (disable for 1.8-style swing) + if (ScaleMeConfig.swingCounterRotation) { + poseStack.mulPose(Axis.YP.rotationDegrees(armSideSign * -ScaleMeConfig.swingPreRotationY)); + } + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java index a8669b5..e17ea22 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java @@ -4,7 +4,6 @@ import com.github.kd_gaming1.scaleme.util.HandContext; import com.mojang.blaze3d.vertex.PoseStack; - import net.minecraft.client.renderer.SubmitNodeCollector; import org.joml.Quaternionf; @@ -14,51 +13,52 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -/** Mixins into {@code LayerRenderState} to apply per-hand item transform overrides. */ +/** Applies the per-hand item transform from {@link HandContext} around each LayerRenderState submit call. */ @Mixin(targets = "net.minecraft.client.renderer.item.ItemStackRenderState$LayerRenderState") public class LayerRenderStateMixin { @Unique private static final float DEG_TO_RAD = (float) (Math.PI / 180.0); - @Unique private boolean scaleme$didPush; + @Unique private boolean pushedPoseThisSubmit = false; - @Unique private final Quaternionf scaleme$tmpQuat = new Quaternionf(); + // Reused every frame to avoid allocation; rotationXYZ overwrites contents each call so sharing is safe + @Unique private final Quaternionf rotationQuaternion = new Quaternionf(); - /** Pushes a transformed pose before the item layer is submitted, if any transform is active. */ @Inject(method = "submit", at = @At("HEAD")) private void onSubmitHead(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { - if (!ScaleMeConfig.enableHandItemTransform) return; - if (HandContext.depth == 0) return; - - float tx = HandContext.tx, ty = HandContext.ty, tz = HandContext.tz; - float rx = HandContext.rx, ry = HandContext.ry, rz = HandContext.rz; - float s = HandContext.s; - if (tx == 0f && ty == 0f && tz == 0f && - rx == 0f && ry == 0f && rz == 0f && s == 1f) return; + if (!ScaleMeConfig.enableHandItemTransform || HandContext.renderDepth == 0) return; + if (!HandContext.hasActiveTransform()) return; poseStack.pushPose(); - scaleme$didPush = true; + pushedPoseThisSubmit = true; - PoseStack.Pose pose = poseStack.last(); + // Work directly on the top Pose to avoid redundant matrix multiplications + PoseStack.Pose topPose = poseStack.last(); - if (tx != 0f || ty != 0f || tz != 0f) - pose.translate(tx, ty, tz); + if (HandContext.translationX != 0f || HandContext.translationY != 0f || HandContext.translationZ != 0f) { + topPose.translate(HandContext.translationX, HandContext.translationY, HandContext.translationZ); + } - if (rx != 0f || ry != 0f || rz != 0f) - pose.rotate(scaleme$tmpQuat.rotationXYZ(rx * DEG_TO_RAD, ry * DEG_TO_RAD, rz * DEG_TO_RAD)); + if (HandContext.rotationX != 0f || HandContext.rotationY != 0f || HandContext.rotationZ != 0f) { + topPose.rotate(rotationQuaternion.rotationXYZ( + HandContext.rotationX * DEG_TO_RAD, + HandContext.rotationY * DEG_TO_RAD, + HandContext.rotationZ * DEG_TO_RAD)); + } - if (s != 1f) - pose.scale(s, s, s); + if (HandContext.scale != 1f) { + topPose.scale(HandContext.scale, HandContext.scale, HandContext.scale); + } } - /** Pops the pose pushed in {@link #onSubmitHead} if one was pushed. */ @Inject(method = "submit", at = @At("RETURN")) private void onSubmitReturn(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { - if (!scaleme$didPush) return; - poseStack.popPose(); - scaleme$didPush = false; + if (pushedPoseThisSubmit) { + poseStack.popPose(); + pushedPoseThisSubmit = false; + } } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java index 5d10d35..5094346 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java @@ -1,23 +1,49 @@ package com.github.kd_gaming1.scaleme.util; +import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; import net.minecraft.world.InteractionHand; -/** - * Shared context for the current first-person hand render pass. - * Populated by {@code ItemInHandRendererMixin} before each item render and cleared after. - */ +/** Holds the resolved transform for whichever hand is currently rendering. Only valid while renderDepth > 0. */ public final class HandContext { - /** {@code null} when outside a first-person hand render. */ public static InteractionHand currentHand = null; + public static int renderDepth = 0; - /** Nesting depth of active hand render passes. */ - public static int depth = 0; - - /** Active transform values for the current hand render. */ - public static float tx, ty, tz; - public static float rx, ry, rz; - public static float s = 1f; + public static float translationX, translationY, translationZ; + public static float rotationX, rotationY, rotationZ; + public static float scale = 1f; private HandContext() {} + + /** Resolves config values for the given hand. Off-hand uses its own sliders only when separately enabled. */ + public static void update(InteractionHand hand) { + currentHand = hand; + + boolean useOffhandTransform = (hand == InteractionHand.OFF_HAND) + && ScaleMeConfig.enableSeparateHandTransforms; + + if (useOffhandTransform) { + translationX = ScaleMeConfig.itemTranslationXOffhand; + translationY = ScaleMeConfig.itemTranslationYOffhand; + translationZ = ScaleMeConfig.itemTranslationZOffhand; + rotationX = ScaleMeConfig.itemRotationXOffhand; + rotationY = ScaleMeConfig.itemRotationYOffhand; + rotationZ = ScaleMeConfig.itemRotationZOffhand; + scale = ScaleMeConfig.itemScaleOffhand; + } else { + translationX = ScaleMeConfig.itemTranslationX; + translationY = ScaleMeConfig.itemTranslationY; + translationZ = ScaleMeConfig.itemTranslationZ; + rotationX = ScaleMeConfig.itemRotationX; + rotationY = ScaleMeConfig.itemRotationY; + rotationZ = ScaleMeConfig.itemRotationZ; + scale = ScaleMeConfig.itemScale; + } + } + + public static boolean hasActiveTransform() { + return translationX != 0f || translationY != 0f || translationZ != 0f + || rotationX != 0f || rotationY != 0f || rotationZ != 0f + || scale != 1f; + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java index 506a179..670e900 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java @@ -3,35 +3,33 @@ import net.azureaaron.hmapi.events.HypixelPacketEvents; import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; -public class HypixelLocationState { +public final class HypixelLocationState { private static boolean onSkyblock = false; - private static boolean inDungeon = false; + private static boolean inDungeon = false; private HypixelLocationState() {} public static void register() { HypixelPacketEvents.LOCATION_UPDATE.register(packet -> { - if (packet instanceof LocationUpdateS2CPacket location) { - onSkyblock = location.serverType() - .map(type -> type.equals("SKYBLOCK")) - .orElse(false); - - inDungeon = onSkyblock && location.map() - .map(map -> map.equals("Dungeon")) - .orElse(false); - } + if (!(packet instanceof LocationUpdateS2CPacket location)) return; + + onSkyblock = location.serverType() + .map("SKYBLOCK"::equals) + .orElse(false); + + // Dungeon is a sub-mode of SkyBlock, so short-circuit when not on SkyBlock + inDungeon = onSkyblock && location.map() + .map("Dungeon"::equals) + .orElse(false); }); } - /** True if the player is on Hypixel SkyBlock (any island/mode). */ public static boolean isOnSkyblock() { return onSkyblock; } - - /** True if the player is in a SkyBlock Dungeon specifically. */ - public static boolean isInDungeon() { return inDungeon; } + public static boolean isInDungeon() { return inDungeon; } public static void reset() { onSkyblock = false; - inDungeon = false; + inDungeon = false; } } \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 970ff08..41cda29 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -80,6 +80,36 @@ "scaleme.midnightconfig.disableSwingAnimation": "Disable Swing Animation", "scaleme.midnightconfig.disableSwingAnimation.tooltip": "Completely removes the arm swing animation. Your arm stays still when you click.", + "scaleme.midnightconfig.enableSwingOverride": "Enable Custom Swing Shape", + "scaleme.midnightconfig.enableSwingOverride.tooltip": "Turns on the custom swing arc and drift sliders below. Disable this to keep the default Minecraft swing shape.", + + "scaleme.midnightconfig.swingArmXScale": "Swing Drift X (vanilla: -0.4)", + "scaleme.midnightconfig.swingArmXScale.tooltip": "Controls how much the item moves left/right during a swing.\nSet to 0 to stop the item lunging toward the center of the screen.", + + "scaleme.midnightconfig.swingArmYScale": "Swing Drift Y (vanilla: 0.2)", + "scaleme.midnightconfig.swingArmYScale.tooltip": "Controls how much the item bobs up and down during a swing.\nSet to 0 to remove the vertical movement entirely.", + + "scaleme.midnightconfig.swingArmZScale": "Swing Drift Z (vanilla: -0.2)", + "scaleme.midnightconfig.swingArmZScale.tooltip": "Controls how much the item pushes forward during a swing.\nSet to 0 to stop the item launching toward the screen.", + + "scaleme.midnightconfig.swingArmXMultiplyBySide": "Mirror Drift X for Left Hand", + "scaleme.midnightconfig.swingArmXMultiplyBySide.tooltip": "When enabled, the X drift is flipped for your off-hand so it mirrors the main hand.\nThis matches vanilla behaviour. Disable for a symmetric drift on both hands.", + + "scaleme.midnightconfig.swingPreRotationY": "Swing Pre-Rotation Y (vanilla: 45)", + "scaleme.midnightconfig.swingPreRotationY.tooltip": "The starting Y angle the item is rotated to before the swing arc begins.\n45° matches vanilla. Lower values keep the item more face-on during the swing.", + + "scaleme.midnightconfig.swingArcYAmount": "Swing Arc Y Rotation (vanilla: -20)", + "scaleme.midnightconfig.swingArcYAmount.tooltip": "How many degrees the item rotates around Y during the swing.\nNegative values rotate it to the left, positive to the right.", + + "scaleme.midnightconfig.swingArcZAmount": "Swing Arc Z Rotation (vanilla: -20)", + "scaleme.midnightconfig.swingArcZAmount.tooltip": "How many degrees the item tilts sideways during the swing.\nNegative values tilt it one way, positive the other.", + + "scaleme.midnightconfig.swingArcXAmount": "Swing Arc X Rotation (vanilla: -80)", + "scaleme.midnightconfig.swingArcXAmount.tooltip": "How many degrees the item swings downward during an attack.\nThis is the main downward chop. Set closer to 0 to reduce the downward arc.", + + "scaleme.midnightconfig.swingCounterRotation": "Swing Counter-Rotation (vanilla: ON)", + "scaleme.midnightconfig.swingCounterRotation.tooltip": "When enabled, the pre-rotation is undone at the end of the arc, which swings the item toward the center of the screen.\nDisable this to keep the item at its held position during the swing, similar to 1.8.9.", + "scaleme.midnightconfig.scaleDesc": "Adjust the size of players and entities in the world", "scaleme.midnightconfig.scaleNameTags": "Let name tags follow the player's scale", From bce3063f7bed9de7b60a1e02596e0aa7e5482559 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:04:48 +0100 Subject: [PATCH 10/16] feat: enhance held item customization with configurable arm position and item transform options --- CHANGELOG.md | 26 +++- .../scaleme/config/ScaleMeConfig.java | 67 +++++++-- .../mixin/ItemInHandRendererMixin.java | 20 +++ .../scaleme/mixin/LayerRenderStateMixin.java | 4 +- .../scaleme/mixin/LivingEntityMixin.java | 9 +- .../kd_gaming1/scaleme/util/HandContext.java | 8 +- .../resources/assets/scaleme/lang/en_us.json | 140 +++++++++++------- 7 files changed, 198 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cbdbd7..c16ad53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ -Rewrite and new features: +Rewrite & New Features -- Added Show Own Nametag in Third Person mode -- Optimized performance for Item Scaling -- Added support for separate scaling between main hand and off hand \ No newline at end of file +Held Item Customisation +- Added fully configurable arm base position (X, Y, Z anchor and height bob scale) +- Added item transform overrides — independently control size, position (X/Y/Z), and rotation (X/Y/Z) of the held item +- Both arm position and item transform support separate values for main hand and off-hand +- Master toggle and per-feature sub-toggles for arm position and item transform independently + +Swing Animation +- Added customisable swing arc — control pre-rotation, Y/Z/X arc amounts, and counter-rotation +- Added customisable swing drift — control X/Y/Z translation during the swing +- Added swing speed multiplier with optional ignore for Haste/Mining Fatigue effects +- Added option to disable swing bobbing +- Added option to disable the swing animation entirely + +Entity Scaling +- Rewrote player and entity scaling to use render state instead of PoseStack transforms, + fixing visual glitches and improving performance +- Added per-tick scale caching to avoid redundant lookups each frame + +Other +- Added Show Own Nametag in Third Person +- Optimised mod performance \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index dbe4e26..7b6d3a3 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -17,13 +17,41 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND) public static boolean enableHandItemTransform = false; - @Entry(category = HAND) - public static boolean enableSeparateHandTransforms = false; + // ── Arm Base Position ─────────────────────────────────────────────────── @Comment(category = HAND, centered = true) public static Comment spacer1; - @Entry(category = HAND, name = "Scale", isSlider = true, min = 0.1f, max = 4f, precision = 1000) + @Comment(category = HAND) + public static Comment armPosDesc; + + @Entry(category = HAND) + public static boolean enableArmPositionOverride = false; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseX = 0.56f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseY = -0.52f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseZ = -0.72f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 0f, precision = 1000) + public static float armHeightScale = -0.6f; + + // ── Item Transform (Main Hand) ────────────────────────────────────────── + + @Comment(category = HAND, centered = true) + public static Comment spacer2; + + @Comment(category = HAND) + public static Comment itemTransformDesc; + + @Entry(category = HAND) + public static boolean enableItemTransformOverride = false; + + @Entry(category = HAND, name = "Scale", isSlider = true, min = 0.1f, max = 3f, precision = 1000) public static float itemScale = 1f; @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) @@ -36,7 +64,7 @@ public class ScaleMeConfig extends MidnightConfig { public static float itemTranslationZ = 0f; @Comment(category = HAND, centered = true) - public static Comment spacer2; + public static Comment spacer3; @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationX = 0f; @@ -47,18 +75,36 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationZ = 0f; - // ── Offhand Item Transform ────────────────────────────────────────────── + // ── Off-Hand Transform ────────────────────────────────────────────────── @Comment(category = HAND, centered = true) - public static Comment spacer3; + public static Comment spacer4; @Comment(category = HAND) public static Comment offhandDesc; + @Entry(category = HAND) + public static boolean enableSeparateHandTransforms = false; + @Comment(category = HAND, centered = true) - public static Comment spacer4; + public static Comment spacer5; - @Entry(category = HAND, isSlider = true, min = 0.1f, max = 4f, precision = 1000) + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseXOffhand = 0.56f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseYOffhand = -0.52f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) + public static float armBaseZOffhand = -0.72f; + + @Entry(category = HAND, isSlider = true, min = -2f, max = 0f, precision = 1000) + public static float armHeightScaleOffhand = -0.6f; + + @Comment(category = HAND, centered = true) + public static Comment spacer6; + + @Entry(category = HAND, isSlider = true, min = 0.1f, max = 3f, precision = 1000) public static float itemScaleOffhand = 1f; @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) @@ -70,6 +116,9 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND, isSlider = true, min = -1f, max = 1f, precision = 1000) public static float itemTranslationZOffhand = 0f; + @Comment(category = HAND, centered = true) + public static Comment spacer7; + @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationXOffhand = 0f; @@ -100,7 +149,7 @@ public class ScaleMeConfig extends MidnightConfig { public static boolean disableSwingAnimation = false; @Comment(category = ANIM, centered = true) - public static Comment spacer5; + public static Comment spacer8; @Entry(category = ANIM) public static boolean enableSwingOverride = false; diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java index b6a3697..999cf21 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -145,4 +145,24 @@ public class ItemInHandRendererMixin { poseStack.mulPose(Axis.YP.rotationDegrees(armSideSign * -ScaleMeConfig.swingPreRotationY)); } } + + /** Replaces the vanilla arm anchor position and height-bob scale with configurable values. + * Vanilla: translate(invert * 0.56, -0.52 + inverseArmHeight * -0.6, -0.72) */ + @Inject(method = "applyItemArmTransform", at = @At("HEAD"), cancellable = true) + private void scaleme$overrideArmBasePosition(PoseStack poseStack, HumanoidArm arm, float inverseArmHeight, CallbackInfo ci) { + if (!ScaleMeConfig.enableHandItemTransform || !ScaleMeConfig.enableArmPositionOverride) return; + ci.cancel(); + + int invert = (arm == HumanoidArm.RIGHT) ? 1 : -1; + + boolean useOffhand = (HandContext.currentHand == InteractionHand.OFF_HAND) + && ScaleMeConfig.enableSeparateHandTransforms; + + float baseX = useOffhand ? ScaleMeConfig.armBaseXOffhand : ScaleMeConfig.armBaseX; + float baseY = useOffhand ? ScaleMeConfig.armBaseYOffhand : ScaleMeConfig.armBaseY; + float baseZ = useOffhand ? ScaleMeConfig.armBaseZOffhand : ScaleMeConfig.armBaseZ; + float heightScale = useOffhand ? ScaleMeConfig.armHeightScaleOffhand : ScaleMeConfig.armHeightScale; + + poseStack.translate(invert * baseX, baseY + inverseArmHeight * heightScale, baseZ); + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java index e17ea22..6c30454 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LayerRenderStateMixin.java @@ -28,8 +28,8 @@ public class LayerRenderStateMixin { private void onSubmitHead(PoseStack poseStack, SubmitNodeCollector collector, int lightCoords, int overlayCoords, int outlineColor, CallbackInfo ci) { - if (!ScaleMeConfig.enableHandItemTransform || HandContext.renderDepth == 0) return; - if (!HandContext.hasActiveTransform()) return; + if (!ScaleMeConfig.enableHandItemTransform || !ScaleMeConfig.enableItemTransformOverride || HandContext.renderDepth == 0) return; + if (!HandContext.activeTransform) return; poseStack.pushPose(); pushedPoseThisSubmit = true; diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java index aab896a..f9b87f3 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/LivingEntityMixin.java @@ -47,13 +47,12 @@ private void modifySwingDuration(CallbackInfoReturnable cir) { /** Suppresses the attack animation for the local player when configured. */ @Inject(method = "getAttackAnim", at = @At("RETURN"), cancellable = true) private void suppressAttackAnim(float partialTick, CallbackInfoReturnable cir) { - if (!ScaleMeConfig.enableAnimOverrides) return; - if (!ScaleMeConfig.disableSwingAnimation) return; + if (!ScaleMeConfig.enableAnimOverrides || !ScaleMeConfig.disableSwingAnimation) return; LivingEntity self = (LivingEntity) (Object) this; + if (!self.level().isClientSide()) return; + var mc = Minecraft.getInstance(); - if (self.level().isClientSide() && self == mc.player) { - cir.setReturnValue(0f); - } + if (self == mc.player) cir.setReturnValue(0f); } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java index 5094346..89cddc4 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HandContext.java @@ -6,6 +6,7 @@ /** Holds the resolved transform for whichever hand is currently rendering. Only valid while renderDepth > 0. */ public final class HandContext { + public static boolean activeTransform = false; public static InteractionHand currentHand = null; public static int renderDepth = 0; @@ -39,11 +40,8 @@ public static void update(InteractionHand hand) { rotationZ = ScaleMeConfig.itemRotationZ; scale = ScaleMeConfig.itemScale; } - } - public static boolean hasActiveTransform() { - return translationX != 0f || translationY != 0f || translationZ != 0f - || rotationX != 0f || rotationY != 0f || rotationZ != 0f - || scale != 1f; + activeTransform = translationX != 0f || translationY != 0f || translationZ != 0f + || rotationX != 0f || rotationY != 0f || rotationZ != 0f || scale != 1f; } } \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 41cda29..721fe61 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -6,30 +6,44 @@ "scaleme.midnightconfig.category.scale": "Entity Scale", "scaleme.midnightconfig.category.view": "Camera & Crosshair", - "scaleme.midnightconfig.handDesc": "Adjust how your held item looks in your hand", - "scaleme.midnightconfig.spacer1": "", - "scaleme.midnightconfig.spacer2": "", - "scaleme.midnightconfig.spacer3": "", - "scaleme.midnightconfig.spacer4": "", - "scaleme.midnightconfig.spacer5": "", + "scaleme.midnightconfig.handDesc": "Master switch for all held item customisation. Both arm position and item transform are controlled by this toggle.", + + "scaleme.midnightconfig.enableHandItemTransform": "Enable Custom Item Transform [MASTER]", + "scaleme.midnightconfig.enableHandItemTransform.tooltip": "Turns on all held item customisation below. Disable this to restore the default Minecraft look entirely.\n\nControls:\n• Arm Base Position (where your hand sits on screen)\n• Item Transform (size, offset, rotation on top of that)\n• Off-Hand variants of both", + + "scaleme.midnightconfig.armPosDesc": "Arm Base Position — replaces the vanilla anchor point where your hand sits on screen", + + "scaleme.midnightconfig.enableArmPositionOverride": "Override Arm Base Position [requires Master ON]", + "scaleme.midnightconfig.enableArmPositionOverride.tooltip": "Replaces the fixed point Minecraft uses to place your hand on screen.\n\nVanilla values: X 0.56 · Y -0.52 · Z -0.72 · Height Scale -0.6\n\nThe Item Transform sliders below are applied on top of this — change this first if the item feels wrong in general, then use Item Transform to fine-tune.", + + "scaleme.midnightconfig.armBaseX": "Arm Anchor X (vanilla: 0.56)", + "scaleme.midnightconfig.armBaseX.tooltip": "How far right your hand sits on screen. Vanilla is 0.56.\nThe Item Transform 'Move Left/Right' slider shifts the item relative to this point.", + + "scaleme.midnightconfig.armBaseY": "Arm Anchor Y (vanilla: -0.52)", + "scaleme.midnightconfig.armBaseY.tooltip": "How far down your hand sits on screen. Vanilla is -0.52.\nThe Item Transform 'Move Up/Down' slider shifts the item relative to this point.", + + "scaleme.midnightconfig.armBaseZ": "Arm Anchor Z (vanilla: -0.72)", + "scaleme.midnightconfig.armBaseZ.tooltip": "How far in front of you your hand sits. Vanilla is -0.72.\nThe Item Transform 'Move Closer/Further' slider shifts the item relative to this point.", + + "scaleme.midnightconfig.armHeightScale": "Arm Height Bob Scale (vanilla: -0.6)", + "scaleme.midnightconfig.armHeightScale.tooltip": "How much your hand moves down as it raises up during the equip/lower animation.\nVanilla is -0.6. Set to 0 to pin the item at a fixed Y regardless of hand height.", - "scaleme.midnightconfig.enableHandItemTransform": "Enable Custom Item Transform", - "scaleme.midnightconfig.enableHandItemTransform.tooltip": "Turns on the custom held item positioning. Disable this to restore the default look and avoid conflicts with other mods.", + "scaleme.midnightconfig.enableItemTransformOverride": "Override Item Transform [requires Master ON]", + "scaleme.midnightconfig.enableItemTransformOverride.tooltip": "Enables the size, position, and rotation sliders below.\nThe item will render at its default position unless this is ON.\n\nNote: Arm Base Position and Item Transform are independent — you can use either or both.", - "scaleme.midnightconfig.enableSeparateHandTransforms": "Enable Separate Off-Hand Transform \n(Off-Hand sliders at the bottom of the page)", - "scaleme.midnightconfig.enableSeparateHandTransforms.tooltip": "Turns on separate positioning for your off-hand item. When disabled, the off-hand uses the same transform as the main hand. Enable this to customise the settings below.", + "scaleme.midnightconfig.itemTransformDesc": "Item Transform — offset applied on top of the arm anchor", "scaleme.midnightconfig.itemScale": "Item Size", "scaleme.midnightconfig.itemScale.tooltip": "How big or small the item in your hand appears.\nSmaller values make it tiny, larger values make it huge.", "scaleme.midnightconfig.itemTranslationX": "Move Item Left / Right", - "scaleme.midnightconfig.itemTranslationX.tooltip": "Shifts the item in your hand to the left or right.\nNegative values move it left, positive values move it right.", + "scaleme.midnightconfig.itemTranslationX.tooltip": "Shifts the item in your hand to the left or right relative to the arm anchor.\nNegative values move it left, positive values move it right.", "scaleme.midnightconfig.itemTranslationY": "Move Item Up / Down", - "scaleme.midnightconfig.itemTranslationY.tooltip": "Shifts the item in your hand up or down.\nNegative values move it down, positive values move it up.", + "scaleme.midnightconfig.itemTranslationY.tooltip": "Shifts the item in your hand up or down relative to the arm anchor.\nNegative values move it down, positive values move it up.", "scaleme.midnightconfig.itemTranslationZ": "Move Item Closer / Further", - "scaleme.midnightconfig.itemTranslationZ.tooltip": "Shifts the item in your hand closer to or further from your screen.\nNegative values push it away, positive values bring it closer.", + "scaleme.midnightconfig.itemTranslationZ.tooltip": "Shifts the item in your hand closer to or further from your screen relative to the arm anchor.\nNegative values push it away, positive values bring it closer.", "scaleme.midnightconfig.itemRotationX": "Tilt Item Up / Down", "scaleme.midnightconfig.itemRotationX.tooltip": "Tilts the item in your hand up or down.", @@ -40,104 +54,128 @@ "scaleme.midnightconfig.itemRotationZ": "Spin Item Clockwise / Counterclockwise", "scaleme.midnightconfig.itemRotationZ.tooltip": "Spins the item in your hand clockwise or counterclockwise.", - "scaleme.midnightconfig.offhandDesc": "Off-Hand Item — requires 'Enable Separate Off-Hand Transform' to be ON", + "scaleme.midnightconfig.offhandDesc": "Off-Hand — requires 'Enable Separate Off-Hand Transform' to be ON\nHas its own arm anchor and item transform. When disabled, off-hand uses the main hand values for everything.", + + "scaleme.midnightconfig.enableSeparateHandTransforms": "Enable Separate Off-Hand Transform [requires Master ON]", + "scaleme.midnightconfig.enableSeparateHandTransforms.tooltip": "Allows the off-hand to use different arm anchor and item transform values from the main hand.\nWhen OFF, all off-hand sliders below are ignored and the main hand values apply to both hands.", + + "scaleme.midnightconfig.armBaseXOffhand": "Off-Hand Arm Anchor X (vanilla: 0.56)", + "scaleme.midnightconfig.armBaseXOffhand.tooltip": "Same as main hand Arm Anchor X but for the off-hand.\nOnly active when 'Enable Separate Off-Hand Transform' is ON and 'Override Arm Base Position' is ON.", + + "scaleme.midnightconfig.armBaseYOffhand": "Off-Hand Arm Anchor Y (vanilla: -0.52)", + "scaleme.midnightconfig.armBaseYOffhand.tooltip": "Same as main hand Arm Anchor Y but for the off-hand.\nOnly active when 'Enable Separate Off-Hand Transform' is ON and 'Override Arm Base Position' is ON.", + + "scaleme.midnightconfig.armBaseZOffhand": "Off-Hand Arm Anchor Z (vanilla: -0.72)", + "scaleme.midnightconfig.armBaseZOffhand.tooltip": "Same as main hand Arm Anchor Z but for the off-hand.\nOnly active when 'Enable Separate Off-Hand Transform' is ON and 'Override Arm Base Position' is ON.", + + "scaleme.midnightconfig.armHeightScaleOffhand": "Off-Hand Height Bob Scale (vanilla: -0.6)", + "scaleme.midnightconfig.armHeightScaleOffhand.tooltip": "Same as main hand Height Bob Scale but for the off-hand.\nOnly active when 'Enable Separate Off-Hand Transform' is ON and 'Override Arm Base Position' is ON.", "scaleme.midnightconfig.itemScaleOffhand": "Off-Hand Item Size", - "scaleme.midnightconfig.itemScaleOffhand.tooltip": "How big or small the off-hand item appears.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemScaleOffhand.tooltip": "How big or small the off-hand item appears.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemTranslationXOffhand": "Move Off-Hand Item Left / Right", - "scaleme.midnightconfig.itemTranslationXOffhand.tooltip": "Shifts the off-hand item to the left or right.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemTranslationXOffhand.tooltip": "Shifts the off-hand item to the left or right relative to its arm anchor.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemTranslationYOffhand": "Move Off-Hand Item Up / Down", - "scaleme.midnightconfig.itemTranslationYOffhand.tooltip": "Shifts the off-hand item up or down.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemTranslationYOffhand.tooltip": "Shifts the off-hand item up or down relative to its arm anchor.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemTranslationZOffhand": "Move Off-Hand Item Closer / Further", - "scaleme.midnightconfig.itemTranslationZOffhand.tooltip": "Shifts the off-hand item closer to or further from your screen.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemTranslationZOffhand.tooltip": "Shifts the off-hand item closer to or further from your screen relative to its arm anchor.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemRotationXOffhand": "Tilt Off-Hand Item Up / Down", - "scaleme.midnightconfig.itemRotationXOffhand.tooltip": "Tilts the off-hand item up or down.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemRotationXOffhand.tooltip": "Tilts the off-hand item up or down.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemRotationYOffhand": "Rotate Off-Hand Item Left / Right", - "scaleme.midnightconfig.itemRotationYOffhand.tooltip": "Rotates the off-hand item to the left or right.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemRotationYOffhand.tooltip": "Rotates the off-hand item to the left or right.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", "scaleme.midnightconfig.itemRotationZOffhand": "Spin Off-Hand Item Clockwise / Counterclockwise", - "scaleme.midnightconfig.itemRotationZOffhand.tooltip": "Spins the off-hand item clockwise or counterclockwise.\nOnly active when 'Enable Separate Off-Hand Transform' is enabled.", + "scaleme.midnightconfig.itemRotationZOffhand.tooltip": "Spins the off-hand item clockwise or counterclockwise.\nOnly active when 'Enable Separate Off-Hand Transform' is ON.", - "scaleme.midnightconfig.animDesc": "Adjust how your held item animates", + "scaleme.midnightconfig.animDesc": "Master switch for all animation customisation. Swing speed, shape, and bobbing are all controlled by this toggle.", - "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes", - "scaleme.midnightconfig.enableAnimOverrides.tooltip": "Turns on the custom animation settings below. Disable this to keep the default Minecraft swing animations.", + "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes [MASTER]", + "scaleme.midnightconfig.enableAnimOverrides.tooltip": "Turns on all custom animation settings below. Disable this to keep the default Minecraft swing animations entirely.\n\nControls:\n• Swing bobbing suppression\n• Swing speed and speed effect ignoring\n• Disable swing animation entirely\n• Custom Swing Shape (arc and drift sliders)", - "scaleme.midnightconfig.disableSwingBobbing": "Stop Item Bobbing While Swinging", - "scaleme.midnightconfig.disableSwingBobbing.tooltip": "Prevents the item from bouncing up and down when you swing it.", + "scaleme.midnightconfig.disableSwingBobbing": "Stop Item Bobbing While Swinging (This overrides Arm Height Bob Scale)", + "scaleme.midnightconfig.disableSwingBobbing.tooltip": "Prevents the item from bouncing up and down when you swing it.\nVanilla bobs the item based on your attack strength cooldown. This locks it steady.", "scaleme.midnightconfig.ignoreSwingSpeedEffects": "Ignore Speed Effects on Swing", - "scaleme.midnightconfig.ignoreSwingSpeedEffects.tooltip": "Potion effects like Haste or Mining Fatigue will no longer change how fast your arm swings.", + "scaleme.midnightconfig.ignoreSwingSpeedEffects.tooltip": "Potion effects like Haste or Mining Fatigue will no longer change how fast your arm swings.\nThe swing will always use the base duration set by the Swing Speed slider.", - "scaleme.midnightconfig.swingAnimationSpeed": "Swing Speed (1 = Disable/Default)", - "scaleme.midnightconfig.swingAnimationSpeed.tooltip": "How fast your arm swings when you attack or use an item.\nLower values slow it down, higher values speed it up.", + "scaleme.midnightconfig.swingAnimationSpeed": "Swing Speed (1.0 = Default)", + "scaleme.midnightconfig.swingAnimationSpeed.tooltip": "How fast your arm swings when you attack or use an item.\nLower values slow it down, higher values speed it up.\n\nIf 'Ignore Speed Effects' is ON, this runs on the base 6-tick duration.\nIf OFF, it multiplies whatever duration Haste/Fatigue would give you.", "scaleme.midnightconfig.disableSwingAnimation": "Disable Swing Animation", - "scaleme.midnightconfig.disableSwingAnimation.tooltip": "Completely removes the arm swing animation. Your arm stays still when you click.", + "scaleme.midnightconfig.disableSwingAnimation.tooltip": "Completely removes the arm swing animation. Your arm stays still when you click.\nOverrides all swing shape settings below.", "scaleme.midnightconfig.enableSwingOverride": "Enable Custom Swing Shape", - "scaleme.midnightconfig.enableSwingOverride.tooltip": "Turns on the custom swing arc and drift sliders below. Disable this to keep the default Minecraft swing shape.", + "scaleme.midnightconfig.enableSwingOverride.tooltip": "Turns on the custom swing arc and drift sliders below, replacing the vanilla swing shape entirely.\nDisable this to keep the default Minecraft swing arc while still using speed or bobbing changes above.", - "scaleme.midnightconfig.swingArmXScale": "Swing Drift X (vanilla: -0.4)", - "scaleme.midnightconfig.swingArmXScale.tooltip": "Controls how much the item moves left/right during a swing.\nSet to 0 to stop the item lunging toward the center of the screen.", + "scaleme.midnightconfig.swingArmXScale": "Swing Drift X (vanilla: -0.4) ", + "scaleme.midnightconfig.swingArmXScale.tooltip": "How much the item moves left/right during a swing.\nVanilla is -0.4, which lunges the item toward the center of the screen. Set to 0 to remove this.", "scaleme.midnightconfig.swingArmYScale": "Swing Drift Y (vanilla: 0.2)", - "scaleme.midnightconfig.swingArmYScale.tooltip": "Controls how much the item bobs up and down during a swing.\nSet to 0 to remove the vertical movement entirely.", + "scaleme.midnightconfig.swingArmYScale.tooltip": "How much the item bobs up and down during a swing.\nVanilla is 0.2. Set to 0 to remove the vertical movement entirely.", "scaleme.midnightconfig.swingArmZScale": "Swing Drift Z (vanilla: -0.2)", - "scaleme.midnightconfig.swingArmZScale.tooltip": "Controls how much the item pushes forward during a swing.\nSet to 0 to stop the item launching toward the screen.", + "scaleme.midnightconfig.swingArmZScale.tooltip": "How much the item pushes toward the screen during a swing.\nVanilla is -0.2. Set to 0 to stop the item launching forward.", "scaleme.midnightconfig.swingArmXMultiplyBySide": "Mirror Drift X for Left Hand", - "scaleme.midnightconfig.swingArmXMultiplyBySide.tooltip": "When enabled, the X drift is flipped for your off-hand so it mirrors the main hand.\nThis matches vanilla behaviour. Disable for a symmetric drift on both hands.", + "scaleme.midnightconfig.swingArmXMultiplyBySide.tooltip": "When ON, the X drift is flipped for the off-hand so it mirrors the main hand.\nThis matches vanilla behaviour. Turn OFF for identical drift direction on both hands.", "scaleme.midnightconfig.swingPreRotationY": "Swing Pre-Rotation Y (vanilla: 45)", - "scaleme.midnightconfig.swingPreRotationY.tooltip": "The starting Y angle the item is rotated to before the swing arc begins.\n45° matches vanilla. Lower values keep the item more face-on during the swing.", + "scaleme.midnightconfig.swingPreRotationY.tooltip": "The Y angle the item is rotated to before the swing arc begins.\nVanilla is 45°. Lower values keep the item more face-on at the start of the swing.", "scaleme.midnightconfig.swingArcYAmount": "Swing Arc Y Rotation (vanilla: -20)", - "scaleme.midnightconfig.swingArcYAmount.tooltip": "How many degrees the item rotates around Y during the swing.\nNegative values rotate it to the left, positive to the right.", + "scaleme.midnightconfig.swingArcYAmount.tooltip": "How many degrees the item rotates around Y during the swing arc.\nVanilla is -20°. Negative rotates left, positive rotates right.", "scaleme.midnightconfig.swingArcZAmount": "Swing Arc Z Rotation (vanilla: -20)", - "scaleme.midnightconfig.swingArcZAmount.tooltip": "How many degrees the item tilts sideways during the swing.\nNegative values tilt it one way, positive the other.", + "scaleme.midnightconfig.swingArcZAmount.tooltip": "How many degrees the item tilts sideways during the swing arc.\nVanilla is -20°.", "scaleme.midnightconfig.swingArcXAmount": "Swing Arc X Rotation (vanilla: -80)", - "scaleme.midnightconfig.swingArcXAmount.tooltip": "How many degrees the item swings downward during an attack.\nThis is the main downward chop. Set closer to 0 to reduce the downward arc.", + "scaleme.midnightconfig.swingArcXAmount.tooltip": "How many degrees the item swings downward during an attack.\nThis is the main downward chop. Vanilla is -80°. Set closer to 0 to reduce the downward arc.", "scaleme.midnightconfig.swingCounterRotation": "Swing Counter-Rotation (vanilla: ON)", - "scaleme.midnightconfig.swingCounterRotation.tooltip": "When enabled, the pre-rotation is undone at the end of the arc, which swings the item toward the center of the screen.\nDisable this to keep the item at its held position during the swing, similar to 1.8.9.", + "scaleme.midnightconfig.swingCounterRotation.tooltip": "When ON, the pre-rotation is undone after the arc, swinging the item back toward the center of the screen. This is vanilla behaviour.\nTurn OFF to keep the item at its pre-rotated angle throughout the swing, similar to 1.8.", "scaleme.midnightconfig.scaleDesc": "Adjust the size of players and entities in the world", - "scaleme.midnightconfig.scaleNameTags": "Let name tags follow the player's scale", - "scaleme.midnightconfig.scaleNameTags.tooltip": "Makes name tags follow the player's scale instead of being fixed at 1.0x.\nThis only affects players and entities that have their own name tag.", + "scaleme.midnightconfig.scaleNameTags": "Scale Name Tags with Entity", + "scaleme.midnightconfig.scaleNameTags.tooltip": "Makes name tags scale with the entity instead of staying fixed at 1.0x size.\nAffects all entities that have a visible name tag.", - "scaleme.midnightconfig.playerScale": "Your Player Scale (1.0 = Disabled)", + "scaleme.midnightconfig.playerScale": "Your Player Scale (1.0 = OFF)", "scaleme.midnightconfig.playerScale.tooltip": "Changes how big or small your own character appears to others.\nDoes not affect your own first-person view.", - "scaleme.midnightconfig.otherPlayersScale": "Other Players Scale (1.0 = Disabled)", + "scaleme.midnightconfig.otherPlayersScale": "Other Players Scale (1.0 = OFF)", "scaleme.midnightconfig.otherPlayersScale.tooltip": "Changes how big or small other players appear to you.", - "scaleme.midnightconfig.villagerNpcScale": "Villager NPC Scale (1.0 = Disabled)", - "scaleme.midnightconfig.villagerNpcScale.tooltip": "Changes how big or small villagers NPC in Hypixel appear.", + "scaleme.midnightconfig.villagerNpcScale": "Villager NPC Scale (1.0 = OFF)", + "scaleme.midnightconfig.villagerNpcScale.tooltip": "Changes how big or small villager NPCs appear.", - "scaleme.midnightconfig.hypixelNpcScale": "Hypixel NPC Scale (1.0 = Disabled)", + "scaleme.midnightconfig.hypixelNpcScale": "Hypixel NPC Scale (1.0 = OFF)", "scaleme.midnightconfig.hypixelNpcScale.tooltip": "Changes how big or small Hypixel NPCs appear.\nOnly has an effect when playing on Hypixel.", "scaleme.midnightconfig.viewDescription": "Control your crosshair and camera behaviour", - "scaleme.midnightconfig.enableCrosshairInThirdPerson": "Show Crosshair Behind You", + "scaleme.midnightconfig.enableCrosshairInThirdPerson": "Show Crosshair in Third Person (Behind)", "scaleme.midnightconfig.enableCrosshairInThirdPerson.tooltip": "Shows the crosshair when the camera is behind your character.", - "scaleme.midnightconfig.enableCrosshairInThirdPersonFront": "Show Crosshair in Front View", + "scaleme.midnightconfig.enableCrosshairInThirdPersonFront": "Show Crosshair in Third Person (Front)", "scaleme.midnightconfig.enableCrosshairInThirdPersonFront.tooltip": "Shows the crosshair when the camera is facing your character from the front.", "scaleme.midnightconfig.disableSelfieCam": "Disable Front Camera View", "scaleme.midnightconfig.disableSelfieCam.tooltip": "Removes the option to flip the camera to face your character.", "scaleme.midnightconfig.showOwnNametagInThirdPerson": "Show Own Nametag in Third Person", - "scaleme.midnightconfig.showOwnNametagInThirdPerson.tooltip": "Shows your own nametag when you are in third person." + "scaleme.midnightconfig.showOwnNametagInThirdPerson.tooltip": "Shows your own nametag when you are in third person.", + + "scaleme.midnightconfig.spacer1": "", + "scaleme.midnightconfig.spacer2": "", + "scaleme.midnightconfig.spacer3": "", + "scaleme.midnightconfig.spacer4": "", + "scaleme.midnightconfig.spacer5": "", + "scaleme.midnightconfig.spacer6": "", + "scaleme.midnightconfig.spacer6a": "", + "scaleme.midnightconfig.spacer7": "" } \ No newline at end of file From 1f328c3e688563231db6f1d0c38bd3b83b49ffdf Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:05:50 +0100 Subject: [PATCH 11/16] feat: update mod version to 3.0.0-beta.1 and change release type to BETA --- build.gradle.kts | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 97d729f..c525a02 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -110,7 +110,7 @@ publishMods { displayName = "${property("mod.name")} ${property("mod.version")} for ${stonecutter.current.version}" version = property("mod.version") as String changelog = rootProject.file("CHANGELOG.md").readText() - type = STABLE + type = BETA modLoaders.add("fabric") dryRun = providers.environmentVariable("MODRINTH_TOKEN").getOrNull() == null diff --git a/gradle.properties b/gradle.properties index 3f40efa..4de94df 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.parallel=true org.gradle.configuration-cache=false # Mod properties -mod.version=3.0.0-alpha.1 +mod.version=3.0.0-beta.1 mod.group=com.github.kd_gaming1 mod.id=scaleme mod.name=Scale Me From 54775c931f0e18694f4fc88f46e6e97666289011 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:18:27 +0100 Subject: [PATCH 12/16] clean up --- src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 91fe140..636aa15 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -19,7 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.xml.stream.events.Comment; import java.util.concurrent.atomic.AtomicBoolean; public class ScaleMe implements ClientModInitializer { @@ -35,9 +34,7 @@ public void onInitializeClient() { HypixelLocationState.register(); HypixelPacketEvents.HELLO.register((packet) -> { - HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> { - map.put(LocationUpdateS2CPacket.ID, 1); - })); + HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1))); hypixelPacketReceived.set(true); }); ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { From 20e93389077c70815625411ccbd3c284930e5ff0 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:25:34 +0100 Subject: [PATCH 13/16] feat: add description for off-hand arm anchor configuration --- .../com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java | 4 ++-- src/main/resources/assets/scaleme/lang/en_us.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 7b6d3a3..8f6d37b 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -86,8 +86,8 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = HAND) public static boolean enableSeparateHandTransforms = false; - @Comment(category = HAND, centered = true) - public static Comment spacer5; + @Comment(category = HAND) + public static Comment armOffhandDesc; @Entry(category = HAND, isSlider = true, min = -2f, max = 2f, precision = 1000) public static float armBaseXOffhand = 0.56f; diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 721fe61..5b4e3ef 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -59,6 +59,8 @@ "scaleme.midnightconfig.enableSeparateHandTransforms": "Enable Separate Off-Hand Transform [requires Master ON]", "scaleme.midnightconfig.enableSeparateHandTransforms.tooltip": "Allows the off-hand to use different arm anchor and item transform values from the main hand.\nWhen OFF, all off-hand sliders below are ignored and the main hand values apply to both hands.", + "scaleme.midnightconfig.armOffhandDesc": "\n(Required: Override Arm Base Position ON)", + "scaleme.midnightconfig.armBaseXOffhand": "Off-Hand Arm Anchor X (vanilla: 0.56)", "scaleme.midnightconfig.armBaseXOffhand.tooltip": "Same as main hand Arm Anchor X but for the off-hand.\nOnly active when 'Enable Separate Off-Hand Transform' is ON and 'Override Arm Base Position' is ON.", @@ -174,7 +176,6 @@ "scaleme.midnightconfig.spacer2": "", "scaleme.midnightconfig.spacer3": "", "scaleme.midnightconfig.spacer4": "", - "scaleme.midnightconfig.spacer5": "", "scaleme.midnightconfig.spacer6": "", "scaleme.midnightconfig.spacer6a": "", "scaleme.midnightconfig.spacer7": "" From 99450041f22cb29ad4b932f6123eb5eb102a2215 Mon Sep 17 00:00:00 2001 From: Kd_Gaming1 <97355992+KdGaming0@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:59:29 +0100 Subject: [PATCH 14/16] moved the is on hypixel to HypixelLocationState --- .../com/github/kd_gaming1/scaleme/ScaleMe.java | 15 +++------------ .../scaleme/util/HypixelLocationState.java | 7 +++++++ .../kd_gaming1/scaleme/util/HypixelNpcUtil.java | 3 +-- .../kd_gaming1/scaleme/util/ScaleResolver.java | 3 +-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 636aa15..44e0ff7 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -5,7 +5,6 @@ import com.github.kd_gaming1.scaleme.util.HypixelLocationState; import eu.midnightdust.lib.config.MidnightConfig; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.azureaaron.hmapi.events.HypixelPacketEvents; import net.azureaaron.hmapi.network.HypixelNetworking; import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; import net.fabricmc.api.ClientModInitializer; @@ -19,28 +18,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.atomic.AtomicBoolean; - public class ScaleMe implements ClientModInitializer { public static final String MOD_ID = "scaleme"; public static final Logger LOGGER = LoggerFactory.getLogger("Scale Me"); - public static final AtomicBoolean hypixelPacketReceived = new AtomicBoolean(false); @Override public void onInitializeClient() { MidnightConfig.init(MOD_ID, ScaleMeConfig.class); + HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1))); + HypixelLocationState.register(); - HypixelPacketEvents.HELLO.register((packet) -> { - HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1))); - hypixelPacketReceived.set(true); - }); - ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { - hypixelPacketReceived.set(false); - HypixelLocationState.reset(); - }); + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> HypixelLocationState.reset()); // Register commands Commands.register(); diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java index 670e900..09d4cd9 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelLocationState.java @@ -5,12 +5,15 @@ public final class HypixelLocationState { + private static boolean onHypixel = false; private static boolean onSkyblock = false; private static boolean inDungeon = false; private HypixelLocationState() {} public static void register() { + HypixelPacketEvents.HELLO.register(packet -> onHypixel = true); + HypixelPacketEvents.LOCATION_UPDATE.register(packet -> { if (!(packet instanceof LocationUpdateS2CPacket location)) return; @@ -25,10 +28,14 @@ public static void register() { }); } + public static boolean isOnHypixel() { return onHypixel; } + public static boolean isOnSkyblock() { return onSkyblock; } + public static boolean isInDungeon() { return inDungeon; } public static void reset() { + onHypixel = false; onSkyblock = false; inDungeon = false; } diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java index dee5bd3..3d9bfc1 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/HypixelNpcUtil.java @@ -1,6 +1,5 @@ package com.github.kd_gaming1.scaleme.util; -import com.github.kd_gaming1.scaleme.ScaleMe; import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.world.scores.PlayerTeam; import net.minecraft.world.scores.Team.Visibility; @@ -21,7 +20,7 @@ private HypixelNpcUtil() {} * Always returns false if not connected to Hypixel. */ public static boolean isHypixelNpc(AbstractClientPlayer player) { - if (!ScaleMe.hypixelPacketReceived.get()) return false; + if (!HypixelLocationState.isOnHypixel()) return false; if (player == null) return false; PlayerTeam team = player.getTeam(); diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java index fd70227..f5ec8fb 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java @@ -1,6 +1,5 @@ package com.github.kd_gaming1.scaleme.util; -import com.github.kd_gaming1.scaleme.ScaleMe; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; import net.minecraft.client.Minecraft; import net.minecraft.client.player.AbstractClientPlayer; @@ -13,7 +12,7 @@ private ScaleResolver() {} public static float resolveScale(Minecraft mc, int entityId) { if (mc.player == null || mc.level == null) return 1f; - boolean onHypixel = ScaleMe.hypixelPacketReceived.get(); + boolean onHypixel = HypixelLocationState.isOnHypixel(); boolean scalingAllowed = !onHypixel || HypixelLocationState.isOnSkyblock(); if (entityId == mc.player.getId()) { From c4bb20c27f9dab4242bba5c408f0570c9ba35dfe Mon Sep 17 00:00:00 2001 From: Kd_Gaming1 <97355992+KdGaming0@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:49:10 +0100 Subject: [PATCH 15/16] feat: implement sword blocking feature with configurable key binding --- CHANGELOG.md | 24 +--------- .../github/kd_gaming1/scaleme/ScaleMe.java | 33 ++++++++++--- .../scaleme/config/ScaleMeConfig.java | 3 ++ .../mixin/ItemInHandRendererMixin.java | 48 +++++++++++++++++++ .../scaleme/util/BlockingState.java | 8 ++++ .../assets/minecraft/lang/en_us.json | 3 ++ .../resources/assets/scaleme/lang/en_us.json | 3 ++ 7 files changed, 94 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/github/kd_gaming1/scaleme/util/BlockingState.java create mode 100644 src/main/resources/assets/minecraft/lang/en_us.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c16ad53..847d0db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,3 @@ -Rewrite & New Features +New Features: -Held Item Customisation -- Added fully configurable arm base position (X, Y, Z anchor and height bob scale) -- Added item transform overrides — independently control size, position (X/Y/Z), and rotation (X/Y/Z) of the held item -- Both arm position and item transform support separate values for main hand and off-hand -- Master toggle and per-feature sub-toggles for arm position and item transform independently - -Swing Animation -- Added customisable swing arc — control pre-rotation, Y/Z/X arc amounts, and counter-rotation -- Added customisable swing drift — control X/Y/Z translation during the swing -- Added swing speed multiplier with optional ignore for Haste/Mining Fatigue effects -- Added option to disable swing bobbing -- Added option to disable the swing animation entirely - -Entity Scaling -- Rewrote player and entity scaling to use render state instead of PoseStack transforms, - fixing visual glitches and improving performance -- Added per-tick scale caching to avoid redundant lookups each frame - -Other -- Added Show Own Nametag in Third Person -- Optimised mod performance \ No newline at end of file +feat: add client side sword block when holding right click with a sword \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 44e0ff7..5c9b2a7 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -2,38 +2,59 @@ import com.github.kd_gaming1.scaleme.command.Commands; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.BlockingState; import com.github.kd_gaming1.scaleme.util.HypixelLocationState; import eu.midnightdust.lib.config.MidnightConfig; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.azureaaron.hmapi.network.HypixelNetworking; import net.azureaaron.hmapi.network.packet.v1.s2c.LocationUpdateS2CPacket; import net.fabricmc.api.ClientModInitializer; - +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; //? if >=1.21.11 { /*import net.minecraft.util.Util; -*///?} else { + *///?} else { import net.minecraft.Util; //?} +import net.minecraft.client.KeyMapping; +import net.minecraft.tags.ItemTags; +import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ScaleMe implements ClientModInitializer { + public static final String MOD_ID = "scaleme"; public static final Logger LOGGER = LoggerFactory.getLogger("Scale Me"); - @Override public void onInitializeClient() { MidnightConfig.init(MOD_ID, ScaleMeConfig.class); - HypixelNetworking.registerToEvents(Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1))); + HypixelNetworking.registerToEvents( + Util.make(new Object2IntOpenHashMap<>(), map -> map.put(LocationUpdateS2CPacket.ID, 1)) + ); HypixelLocationState.register(); - ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> HypixelLocationState.reset()); + ClientPlayConnectionEvents.DISCONNECT.register( + (handler, client) -> HypixelLocationState.reset() + ); + + KeyMapping blockKey = KeyBindingHelper.registerKeyBinding(new KeyMapping( + "key.scaleme.sword_block", + GLFW.GLFW_MOUSE_BUTTON_RIGHT, + KeyMapping.Category.GAMEPLAY + )); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (client.player == null) return; + BlockingState.isBlocking = ScaleMeConfig.enableSwordBlock + && blockKey.isDown() + && client.player.getMainHandItem().is(ItemTags.SWORDS); + }); - // Register commands Commands.register(); } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 8f6d37b..7603b06 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -136,6 +136,9 @@ public class ScaleMeConfig extends MidnightConfig { @Entry(category = ANIM) public static boolean enableAnimOverrides = false; + @Entry(category = ANIM) + public static boolean enableSwordBlock = false; + @Entry(category = ANIM) public static boolean disableSwingBobbing = false; diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java index 999cf21..ef711a1 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -1,6 +1,7 @@ package com.github.kd_gaming1.scaleme.mixin; import com.github.kd_gaming1.scaleme.config.ScaleMeConfig; +import com.github.kd_gaming1.scaleme.util.BlockingState; import com.github.kd_gaming1.scaleme.util.HandContext; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; @@ -10,13 +11,16 @@ import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.renderer.ItemInHandRenderer; import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.tags.ItemTags; import net.minecraft.world.entity.HumanoidArm; import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import net.minecraft.util.Mth; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -25,6 +29,7 @@ @Mixin(ItemInHandRenderer.class) public class ItemInHandRendererMixin { + @Unique private static final float PI = (float) Math.PI; @Shadow private void applyItemArmTransform(PoseStack poseStack, HumanoidArm arm, float inverseArmHeight) {} @@ -165,4 +170,47 @@ public class ItemInHandRendererMixin { poseStack.translate(invert * baseX, baseY + inverseArmHeight * heightScale, baseZ); } + + /** Applies a blocking pose when holding a sword and right-clicking. */ + @Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true) + private void scaleme$applySwordBlockPose( + AbstractClientPlayer player, + float tickDelta, float pitch, + InteractionHand hand, + float swingProgress, + ItemStack heldItem, + float equipProgress, + PoseStack poseStack, + SubmitNodeCollector collector, + int packedLight, + CallbackInfo ci) { + + if (!BlockingState.isBlocking || hand != InteractionHand.MAIN_HAND || !heldItem.is(ItemTags.SWORDS)) return; + ci.cancel(); + + poseStack.pushPose(); + + HumanoidArm arm = player.getMainArm(); + int armSideSign = (arm == HumanoidArm.RIGHT) ? 1 : -1; + + applyItemArmTransform(poseStack, arm, equipProgress); + poseStack.translate((float) armSideSign * -0.14142136F, 0.08F, 0.14142136F); + poseStack.mulPose(Axis.XP.rotationDegrees(-102.25F)); + poseStack.mulPose(Axis.YP.rotationDegrees((float) armSideSign * 13.365F)); + poseStack.mulPose(Axis.ZP.rotationDegrees((float) armSideSign * 78.05F)); + + // Self-cast required: Mixin class can't directly extend ItemInHandRenderer + ((ItemInHandRenderer) (Object) this).renderItem( + player, + heldItem, + arm == HumanoidArm.RIGHT + ? ItemDisplayContext.FIRST_PERSON_RIGHT_HAND + : ItemDisplayContext.FIRST_PERSON_LEFT_HAND, + poseStack, + collector, + packedLight + ); + + poseStack.popPose(); + } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/BlockingState.java b/src/main/java/com/github/kd_gaming1/scaleme/util/BlockingState.java new file mode 100644 index 0000000..cb54422 --- /dev/null +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/BlockingState.java @@ -0,0 +1,8 @@ +package com.github.kd_gaming1.scaleme.util; + +/** Shared state indicating whether the player is currently in a sword-block pose. */ +public final class BlockingState { + public static boolean isBlocking = false; + + private BlockingState() {} +} \ No newline at end of file diff --git a/src/main/resources/assets/minecraft/lang/en_us.json b/src/main/resources/assets/minecraft/lang/en_us.json new file mode 100644 index 0000000..f63bb4c --- /dev/null +++ b/src/main/resources/assets/minecraft/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "key.scaleme.sword_block": "Sword Block" +} \ No newline at end of file diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 5b4e3ef..44aba73 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -96,6 +96,9 @@ "scaleme.midnightconfig.animDesc": "Master switch for all animation customisation. Swing speed, shape, and bobbing are all controlled by this toggle.", + "scaleme.midnightconfig.enableSwordBlock": "Enable Sword Block [does not requires Animation Master]", + "scaleme.midnightconfig.enableSwordBlock.tooltip": "Lets you hold right-click with a sword to enter a blocking pose, similar to pre-1.9.\nThe keybind defaults to right mouse button — rebind it in Controls if needed.", + "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes [MASTER]", "scaleme.midnightconfig.enableAnimOverrides.tooltip": "Turns on all custom animation settings below. Disable this to keep the default Minecraft swing animations entirely.\n\nControls:\n• Swing bobbing suppression\n• Swing speed and speed effect ignoring\n• Disable swing animation entirely\n• Custom Swing Shape (arc and drift sliders)", From 501ee42ad416069b74d92a72497dd0cf799a2a51 Mon Sep 17 00:00:00 2001 From: KdGaming0 <97355992+KdGaming0@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:12:10 +0100 Subject: [PATCH 16/16] feat: update mod version to 3.0.0 and change release type to STABLE; enhance sword block feature and fix NPC scaling --- CHANGELOG.md | 3 +- build.gradle.kts | 2 +- gradle.properties | 2 +- .../github/kd_gaming1/scaleme/ScaleMe.java | 2 ++ .../scaleme/config/ScaleMeConfig.java | 11 +++--- .../mixin/ItemInHandRendererMixin.java | 36 ++++++++++--------- .../scaleme/util/ScaleResolver.java | 4 +-- .../resources/assets/scaleme/lang/en_us.json | 2 +- 8 files changed, 35 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 847d0db..e4400d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ New Features: -feat: add client side sword block when holding right click with a sword \ No newline at end of file +feat: Add client-side sword block when holding right-click with a sword +fix: Hypixel NPC scaling when chasing the other player scale slider. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index c525a02..97d729f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -110,7 +110,7 @@ publishMods { displayName = "${property("mod.name")} ${property("mod.version")} for ${stonecutter.current.version}" version = property("mod.version") as String changelog = rootProject.file("CHANGELOG.md").readText() - type = BETA + type = STABLE modLoaders.add("fabric") dryRun = providers.environmentVariable("MODRINTH_TOKEN").getOrNull() == null diff --git a/gradle.properties b/gradle.properties index 4de94df..8435015 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.parallel=true org.gradle.configuration-cache=false # Mod properties -mod.version=3.0.0-beta.1 +mod.version=3.0.0 mod.group=com.github.kd_gaming1 mod.id=scaleme mod.name=Scale Me diff --git a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java index 5c9b2a7..917771f 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/ScaleMe.java @@ -17,6 +17,7 @@ *///?} else { import net.minecraft.Util; //?} +import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.KeyMapping; import net.minecraft.tags.ItemTags; import org.lwjgl.glfw.GLFW; @@ -44,6 +45,7 @@ public void onInitializeClient() { KeyMapping blockKey = KeyBindingHelper.registerKeyBinding(new KeyMapping( "key.scaleme.sword_block", + InputConstants.Type.MOUSE, GLFW.GLFW_MOUSE_BUTTON_RIGHT, KeyMapping.Category.GAMEPLAY )); diff --git a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java index 7603b06..2801ef0 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/config/ScaleMeConfig.java @@ -102,7 +102,7 @@ public class ScaleMeConfig extends MidnightConfig { public static float armHeightScaleOffhand = -0.6f; @Comment(category = HAND, centered = true) - public static Comment spacer6; + public static Comment spacer5; @Entry(category = HAND, isSlider = true, min = 0.1f, max = 3f, precision = 1000) public static float itemScaleOffhand = 1f; @@ -117,7 +117,7 @@ public class ScaleMeConfig extends MidnightConfig { public static float itemTranslationZOffhand = 0f; @Comment(category = HAND, centered = true) - public static Comment spacer7; + public static Comment spacer6; @Entry(category = HAND, isSlider = true, min = -180f, max = 180f, precision = 10) public static float itemRotationXOffhand = 0f; @@ -134,10 +134,13 @@ public class ScaleMeConfig extends MidnightConfig { public static Comment animDesc; @Entry(category = ANIM) - public static boolean enableAnimOverrides = false; + public static boolean enableSwordBlock = false; + + @Comment(category = HAND, centered = true) + public static Comment spacer7; @Entry(category = ANIM) - public static boolean enableSwordBlock = false; + public static boolean enableAnimOverrides = false; @Entry(category = ANIM) public static boolean disableSwingBobbing = false; diff --git a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java index ef711a1..1ac6f2d 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/mixin/ItemInHandRendererMixin.java @@ -174,32 +174,28 @@ public class ItemInHandRendererMixin { /** Applies a blocking pose when holding a sword and right-clicking. */ @Inject(method = "renderArmWithItem", at = @At("HEAD"), cancellable = true) private void scaleme$applySwordBlockPose( - AbstractClientPlayer player, - float tickDelta, float pitch, - InteractionHand hand, - float swingProgress, - ItemStack heldItem, - float equipProgress, - PoseStack poseStack, - SubmitNodeCollector collector, - int packedLight, + AbstractClientPlayer player, float tickDelta, float pitch, + InteractionHand hand, float swingProgress, ItemStack heldItem, + float equipProgress, PoseStack poseStack, + SubmitNodeCollector collector, int packedLight, CallbackInfo ci) { - if (!BlockingState.isBlocking || hand != InteractionHand.MAIN_HAND || !heldItem.is(ItemTags.SWORDS)) return; + if (!BlockingState.isBlocking + || hand != InteractionHand.MAIN_HAND + || !heldItem.is(ItemTags.SWORDS)) return; + ci.cancel(); poseStack.pushPose(); - HumanoidArm arm = player.getMainArm(); - int armSideSign = (arm == HumanoidArm.RIGHT) ? 1 : -1; + int side = arm == HumanoidArm.RIGHT ? 1 : -1; applyItemArmTransform(poseStack, arm, equipProgress); - poseStack.translate((float) armSideSign * -0.14142136F, 0.08F, 0.14142136F); + poseStack.translate(side * -0.14142136F, 0.08F, 0.14142136F); poseStack.mulPose(Axis.XP.rotationDegrees(-102.25F)); - poseStack.mulPose(Axis.YP.rotationDegrees((float) armSideSign * 13.365F)); - poseStack.mulPose(Axis.ZP.rotationDegrees((float) armSideSign * 78.05F)); + poseStack.mulPose(Axis.YP.rotationDegrees(side * 13.365F)); + poseStack.mulPose(Axis.ZP.rotationDegrees(side * 78.05F)); - // Self-cast required: Mixin class can't directly extend ItemInHandRenderer ((ItemInHandRenderer) (Object) this).renderItem( player, heldItem, @@ -210,7 +206,13 @@ public class ItemInHandRendererMixin { collector, packedLight ); - poseStack.popPose(); + + // --- release HandContext --- // + HandContext.renderDepth--; + if (HandContext.renderDepth <= 0) { + HandContext.renderDepth = 0; + HandContext.currentHand = null; + } } } \ No newline at end of file diff --git a/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java index f5ec8fb..ce8fae7 100644 --- a/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java +++ b/src/main/java/com/github/kd_gaming1/scaleme/util/ScaleResolver.java @@ -16,10 +16,10 @@ public static float resolveScale(Minecraft mc, int entityId) { boolean scalingAllowed = !onHypixel || HypixelLocationState.isOnSkyblock(); if (entityId == mc.player.getId()) { - return scalingAllowed ? ScaleMeConfig.playerScale : 1f; + return ScaleMeConfig.playerScale; } - if (onHypixel && ScaleMeConfig.hypixelNpcScale != 1f) { + if (onHypixel) { var entity = mc.level.getEntity(entityId); if (entity instanceof AbstractClientPlayer player && HypixelNpcUtil.isHypixelNpc(player)) { return HypixelLocationState.isInDungeon() ? 1f : ScaleMeConfig.hypixelNpcScale; diff --git a/src/main/resources/assets/scaleme/lang/en_us.json b/src/main/resources/assets/scaleme/lang/en_us.json index 44aba73..6c05f0a 100644 --- a/src/main/resources/assets/scaleme/lang/en_us.json +++ b/src/main/resources/assets/scaleme/lang/en_us.json @@ -96,7 +96,7 @@ "scaleme.midnightconfig.animDesc": "Master switch for all animation customisation. Swing speed, shape, and bobbing are all controlled by this toggle.", - "scaleme.midnightconfig.enableSwordBlock": "Enable Sword Block [does not requires Animation Master]", + "scaleme.midnightconfig.enableSwordBlock": "Enable Sword Block", "scaleme.midnightconfig.enableSwordBlock.tooltip": "Lets you hold right-click with a sword to enter a blocking pose, similar to pre-1.9.\nThe keybind defaults to right mouse button — rebind it in Controls if needed.", "scaleme.midnightconfig.enableAnimOverrides": "Enable Animation Changes [MASTER]",