diff options
| author | viciscat <51047087+viciscat@users.noreply.github.com> | 2025-06-01 02:25:16 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-01 08:25:16 +0800 |
| commit | 4c822a6383d8f92dc850c82b93fa6cd4a67f2610 (patch) | |
| tree | 714dcfd9eaa66da04546832f2a27574843d280d3 | |
| parent | 86f36d661f551f8639b30fb39312b6b9290b8a02 (diff) | |
| download | Skyblocker-4c822a6383d8f92dc850c82b93fa6cd4a67f2610.tar.gz Skyblocker-4c822a6383d8f92dc850c82b93fa6cd4a67f2610.tar.bz2 Skyblocker-4c822a6383d8f92dc850c82b93fa6cd4a67f2610.zip | |
Epic Armor Customization GUI (#1215)
* start creating the screen
* trim selection
* colo(u)ring and button in inventory
* translation and little touches
* translation and little touches
* things i forgot
* remove debug stuff oops
* replace speed by duration
* 1.12.5
- Remake the trim buttons because mojang hates me
- Undo the OkLabColor cache
* some docs and things
* requested changes
* fix crash in tests
* changes!
* clean up
* Test animated dye interpolation
* Add DFU for animated dye and item background
* Add slider steps
* Add config data fixer test
* Draw trim material item
* remove old stuff
* Clean up interpolate and gui code
* supplier
* 180
* grrrrr
---------
Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>
42 files changed, 2728 insertions, 205 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java index 053d89be..566a426d 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java @@ -30,7 +30,7 @@ import java.util.function.Consumer; import org.apache.commons.lang3.function.Consumers; public class SkyblockerConfigManager { - public static final int CONFIG_VERSION = 3; + public static final int CONFIG_VERSION = 4; private static final Path CONFIG_FILE = FabricLoader.getInstance().getConfigDir().resolve("skyblocker.json"); private static final ConfigClassHandler<SkyblockerConfig> HANDLER = ConfigClassHandler.createBuilder(SkyblockerConfig.class) .serializer(config -> GsonConfigSerializerBuilder.create(config) diff --git a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java index 2605d5ef..258d43fa 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java @@ -1,8 +1,8 @@ package de.hysky.skyblocker.config.configs; import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.skyblock.item.CustomArmorAnimatedDyes; -import de.hysky.skyblocker.skyblock.item.CustomArmorTrims; +import de.hysky.skyblocker.skyblock.item.custom.CustomArmorAnimatedDyes; +import de.hysky.skyblocker.skyblock.item.custom.CustomArmorTrims; import de.hysky.skyblocker.skyblock.item.slottext.SlotTextMode; import dev.isxander.yacl3.config.v2.api.SerialEntry; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; diff --git a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java index b887d415..1762dfdb 100644 --- a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java +++ b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java @@ -68,6 +68,8 @@ public class ConfigDataFixer { builder.addFixer(new ConfigFix1(schema2, true)); Schema schema3 = builder.addSchema(3, Schema::new); builder.addFixer(new ConfigFix2QuickNav(schema3, true)); + Schema schema4 = builder.addSchema(4, Schema::new); + builder.addFixer(new ConfigFix3AnimatedDyeAndItemBackground(schema4, true)); return builder.build().fixer(); } diff --git a/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix3AnimatedDyeAndItemBackground.java b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix3AnimatedDyeAndItemBackground.java new file mode 100644 index 00000000..20032a1c --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix3AnimatedDyeAndItemBackground.java @@ -0,0 +1,54 @@ +package de.hysky.skyblocker.config.datafixer; + +import com.mojang.datafixers.DSL; +import com.mojang.datafixers.TypeRewriteRule; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import net.minecraft.util.math.ColorHelper; + +import java.util.stream.Stream; + +public class ConfigFix3AnimatedDyeAndItemBackground extends ConfigDataFix { + public ConfigFix3AnimatedDyeAndItemBackground(Schema outputSchema, boolean changesType) { + super(outputSchema, changesType); + } + + @Override + protected TypeRewriteRule makeRule() { + return fixTypeEverywhereTyped( + "ConfigFix3AnimatedDyeAndItemBackground", + getInputSchema().getType(ConfigDataFixer.CONFIG_TYPE), + typed -> typed.update(DSL.remainderFinder(), this::fix) + ); + } + + private <T> Dynamic<T> fix(Dynamic<T> dynamic) { + return fixVersion(dynamic).update("general", general -> general + .update("customAnimatedDyes", customAnimatedDyes -> customAnimatedDyes + .updateMapValues(customAnimatedDye -> customAnimatedDye.mapSecond(this::fixCustomAnimatedDye)) + ) + .update("itemInfoDisplay", itemInfoDisplay -> itemInfoDisplay + .renameField("itemRarityBackgroundStyle", "itemBackgroundStyle") + .renameField("itemRarityBackgroundsOpacity", "itemBackgroundOpacity") + ) + ); + } + + private <T> Dynamic<T> fixCustomAnimatedDye(Dynamic<T> customAnimatedDye) { + return customAnimatedDye + .set("keyframes", customAnimatedDye.createList(Stream.of( + customAnimatedDye.emptyMap() + .set("color", customAnimatedDye.createInt(ColorHelper.fullAlpha(customAnimatedDye.get("color1").asInt(0)))) + .set("time", customAnimatedDye.createFloat(0)), + customAnimatedDye.emptyMap() + .set("color", customAnimatedDye.createInt(ColorHelper.fullAlpha(customAnimatedDye.get("color2").asInt(0)))) + .set("time", customAnimatedDye.createFloat(1)) + ))) + .remove("color1") + .remove("color2") + // Samples is how many steps the animation has, and tickDelay is how long each step takes in ticks. + .set("duration", customAnimatedDye.createFloat(customAnimatedDye.get("samples").asInt(20) * customAnimatedDye.get("tickDelay").asInt(1) / 20f)) + .remove("samples") + .remove("tickDelay"); + } +} diff --git a/src/main/java/de/hysky/skyblocker/debug/Debug.java b/src/main/java/de/hysky/skyblocker/debug/Debug.java index bad504f2..bfee4471 100644 --- a/src/main/java/de/hysky/skyblocker/debug/Debug.java +++ b/src/main/java/de/hysky/skyblocker/debug/Debug.java @@ -10,7 +10,7 @@ import de.hysky.skyblocker.mixins.accessors.HandledScreenAccessor; import de.hysky.skyblocker.skyblock.events.EventNotifications; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer; +import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.networth.NetworthCalculator; import net.azureaaron.networth.Calculation; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -150,7 +150,7 @@ public class Debug { JSON { @Override public Text format(ItemStack stack) { - return Text.literal(SkyblockerMod.GSON_COMPACT.toJson(ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.encodeStart(ItemStackComponentizationFixer.getRegistryLookup().getOps(JsonOps.INSTANCE), stack).getOrThrow())); + return Text.literal(SkyblockerMod.GSON_COMPACT.toJson(ItemUtils.EMPTY_ALLOWING_ITEMSTACK_CODEC.encodeStart(Utils.getRegistryWrapperLookup().getOps(JsonOps.INSTANCE), stack).getOrThrow())); } }, SNBT { diff --git a/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java index 177299bf..eab413e3 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.At; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.CustomArmorTrims; +import de.hysky.skyblocker.skyblock.item.custom.CustomArmorTrims; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; diff --git a/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java b/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java index 12022b7c..dfb9dba8 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java @@ -4,7 +4,7 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.skyblock.item.CustomArmorAnimatedDyes; +import de.hysky.skyblocker.skyblock.item.custom.CustomArmorAnimatedDyes; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import net.minecraft.component.type.DyedColorComponent; diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/EntityRenderDispatcherAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/EntityRenderDispatcherAccessor.java new file mode 100644 index 00000000..da72c791 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/EntityRenderDispatcherAccessor.java @@ -0,0 +1,12 @@ +package de.hysky.skyblocker.mixins.accessors; + +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(EntityRenderDispatcher.class) +public interface EntityRenderDispatcherAccessor { + @Accessor + EquipmentModelLoader getEquipmentModelLoader(); +} diff --git a/src/main/java/de/hysky/skyblocker/mixins/accessors/SpriteContentsAccessor.java b/src/main/java/de/hysky/skyblocker/mixins/accessors/SpriteContentsAccessor.java new file mode 100644 index 00000000..e77637b1 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/mixins/accessors/SpriteContentsAccessor.java @@ -0,0 +1,12 @@ +package de.hysky.skyblocker.mixins.accessors; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.SpriteContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SpriteContents.class) +public interface SpriteContentsAccessor { + @Accessor + NativeImage getImage(); +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java deleted file mode 100644 index 48f345c4..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java +++ /dev/null @@ -1,156 +0,0 @@ -package de.hysky.skyblocker.skyblock.item; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.BoolArgumentType; -import com.mojang.brigadier.arguments.IntegerArgumentType; -import de.hysky.skyblocker.SkyblockerMod; -import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.OkLabColor; -import de.hysky.skyblocker.utils.Utils; -import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; -import dev.isxander.yacl3.config.v2.api.SerialEntry; -import it.unimi.dsi.fastutil.objects.Object2ObjectFunction; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.item.ItemStack; -import net.minecraft.registry.tag.ItemTags; -import net.minecraft.text.Text; - -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; -import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; - -public class CustomArmorAnimatedDyes { - private static final Object2ObjectOpenHashMap<AnimatedDye, AnimatedDyeStateTracker> STATE_TRACKER_MAP = new Object2ObjectOpenHashMap<>(); - private static final Object2ObjectFunction<AnimatedDye, AnimatedDyeStateTracker> NEW_STATE_TRACKER = _dye -> AnimatedDyeStateTracker.create(); - private static final int DEFAULT_TICK_DELAY = 4; - private static int ticks; - - @Init - public static void init() { - ClientCommandRegistrationCallback.EVENT.register(CustomArmorAnimatedDyes::registerCommands); - ClientTickEvents.END_CLIENT_TICK.register(_client -> ++ticks); - } - - private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(literal(SkyblockerMod.NAMESPACE) - .then(literal("custom") - .then(literal("animatedDye") - .executes(context -> customizeAnimatedDye(context.getSource(), Integer.MIN_VALUE, Integer.MIN_VALUE, 0, false, 0)) - .then(argument("hex1", ColorArgumentType.hex()) - .then(argument("hex2", ColorArgumentType.hex()) - .then(argument("samples", IntegerArgumentType.integer(1)) - .then(argument("cycleBack", BoolArgumentType.bool()) - .executes(context -> customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), DEFAULT_TICK_DELAY)) - .then(argument("tickDelay", IntegerArgumentType.integer(0, 20)) - .executes(context ->customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), IntegerArgumentType.getInteger(context, "tickDelay"))))))))))); - } - - private static int customizeAnimatedDye(FabricClientCommandSource source, int color1, int color2, int samples, boolean cycleBack, int tickDelay) { - ItemStack heldItem = source.getPlayer().getMainHandStack(); - - if (Utils.isOnSkyblock() && heldItem != null && !heldItem.isEmpty()) { - if (heldItem.isIn(ItemTags.DYEABLE)) { - String itemUuid = ItemUtils.getItemUuid(heldItem); - - if (!itemUuid.isEmpty()) { - Object2ObjectOpenHashMap<String, AnimatedDye> customAnimatedDyes = SkyblockerConfigManager.get().general.customAnimatedDyes; - - if (color1 == Integer.MIN_VALUE && color2 == Integer.MIN_VALUE) { - if (customAnimatedDyes.containsKey(itemUuid)) { - customAnimatedDyes.remove(itemUuid); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.removed"))); - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.neverHad"))); - } - } else { - AnimatedDye animatedDye = new AnimatedDye(color1, color2, samples, cycleBack, tickDelay); - - customAnimatedDyes.put(itemUuid, animatedDye); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.added"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.noItemUuid"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.notDyeable"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.unableToSetDye"))); - } - - return Command.SINGLE_SUCCESS; - } - - public static int animateColorTransition(AnimatedDye animatedDye) { - AnimatedDyeStateTracker trackedState = STATE_TRACKER_MAP.computeIfAbsent(animatedDye, NEW_STATE_TRACKER); - - if (trackedState.lastRecordedTick + animatedDye.tickDelay() > ticks) { - return trackedState.lastColor; - } - - trackedState.lastRecordedTick = ticks; - - return animatedDye.interpolate(trackedState); - } - - private static class AnimatedDyeStateTracker { - private int sampleCounter; - private boolean onBackCycle = false; - private int lastColor = 0; - private int lastRecordedTick = 0; - - boolean shouldCycleBack(int samples, boolean canCycleBack) { - return canCycleBack && sampleCounter == samples; - } - - int getAndDecrement() { - return sampleCounter--; - } - - int getAndIncrement() { - return sampleCounter++; - } - - static AnimatedDyeStateTracker create() { - return new AnimatedDyeStateTracker(); - } - } - - public record AnimatedDye(@SerialEntry int color1, @SerialEntry int color2, @SerialEntry int samples, @SerialEntry boolean cycleBack, @SerialEntry int tickDelay) { - - private int interpolate(AnimatedDyeStateTracker stateTracker) { - if (stateTracker.shouldCycleBack(samples, cycleBack)) stateTracker.onBackCycle = true; - - if (stateTracker.onBackCycle) { - float percent = (1f / samples) * stateTracker.getAndDecrement(); - - //Go back to normal cycle once we've cycled all the way back - if (stateTracker.sampleCounter == 0) stateTracker.onBackCycle = false; - - int interpolatedColor = OkLabColor.interpolate(color1, color2, percent); - stateTracker.lastColor = interpolatedColor; - - return interpolatedColor; - } - - //This will only happen if cycleBack is false - if (stateTracker.sampleCounter == samples) stateTracker.sampleCounter = 0; - - float percent = (1f / samples) * stateTracker.getAndIncrement(); - int interpolatedColor = OkLabColor.interpolate(color1, color2, percent); - - stateTracker.lastColor = interpolatedColor; - - return interpolatedColor; - } - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyes.java b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyes.java new file mode 100644 index 00000000..34110513 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyes.java @@ -0,0 +1,180 @@ +package de.hysky.skyblocker.skyblock.item.custom; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.FloatArgumentType; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.OkLabColor; +import de.hysky.skyblocker.utils.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.text.Text; +import org.jetbrains.annotations.VisibleForTesting; + +import java.util.List; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class CustomArmorAnimatedDyes { + private static final Object2ObjectOpenHashMap<AnimatedDye, AnimatedDyeStateTracker> STATE_TRACKER_MAP = new Object2ObjectOpenHashMap<>(); + private static final float DEFAULT_DELAY = 0; + private static int frames; + + @Init + public static void init() { + ClientCommandRegistrationCallback.EVENT.register(CustomArmorAnimatedDyes::registerCommands); + WorldRenderEvents.START.register(ignored -> ++frames); + // have the animation restart on world change because why not? + SkyblockEvents.LOCATION_CHANGE.register(ignored -> cleanTrackers()); + } + + private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(literal(SkyblockerMod.NAMESPACE) + .then(literal("custom") + .then(literal("animatedDye") + .executes(context -> customizeAnimatedDye(context.getSource(), Integer.MIN_VALUE, Integer.MIN_VALUE, 0, false, 0)) + .then(argument("hex1", ColorArgumentType.hex()) + .then(argument("hex2", ColorArgumentType.hex()) + .then(argument("duration", FloatArgumentType.floatArg(0.1f, 10f)) + .then(argument("cycleBack", BoolArgumentType.bool()) + .executes(context -> customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), FloatArgumentType.getFloat(context, "duration"), BoolArgumentType.getBool(context, "cycleBack"), DEFAULT_DELAY)) + .then(argument("delay", FloatArgumentType.floatArg(0)) + .executes(context ->customizeAnimatedDye(context.getSource(), ColorArgumentType.getIntFromHex(context, "hex1"), ColorArgumentType.getIntFromHex(context, "hex2"), FloatArgumentType.getFloat(context, "duration"), BoolArgumentType.getBool(context, "cycleBack"), FloatArgumentType.getFloat(context, "delay"))))))))))); + } + + private static int customizeAnimatedDye(FabricClientCommandSource source, int color1, int color2, float duration, boolean cycleBack, float delay) { + ItemStack heldItem = source.getPlayer().getMainHandStack(); + + if (Utils.isOnSkyblock() && heldItem != null && !heldItem.isEmpty()) { + if (heldItem.isIn(ItemTags.DYEABLE)) { + String itemUuid = ItemUtils.getItemUuid(heldItem); + + if (!itemUuid.isEmpty()) { + Object2ObjectOpenHashMap<String, AnimatedDye> customAnimatedDyes = SkyblockerConfigManager.get().general.customAnimatedDyes; + + if (color1 == Integer.MIN_VALUE && color2 == Integer.MIN_VALUE) { + if (customAnimatedDyes.containsKey(itemUuid)) { + SkyblockerConfigManager.update(config -> config.general.customAnimatedDyes.remove(itemUuid)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.removed"))); + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.neverHad"))); + } + } else { + AnimatedDye animatedDye = new AnimatedDye(List.of(new Keyframe(color1, 0), new Keyframe(color2, 1)), cycleBack, delay, duration); + + SkyblockerConfigManager.update(config -> config.general.customAnimatedDyes.put(itemUuid, animatedDye)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.added"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.noItemUuid"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.notDyeable"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.unableToSetDye"))); + } + + return Command.SINGLE_SUCCESS; + } + + public static int animateColorTransition(AnimatedDye animatedDye) { + AnimatedDyeStateTracker trackedState = STATE_TRACKER_MAP.computeIfAbsent(animatedDye, AnimatedDyeStateTracker::new); + + if (trackedState.lastRecordedFrame == frames) { + return trackedState.lastColor; + } + + trackedState.lastRecordedFrame = frames; + + return trackedState.interpolate(animatedDye, MinecraftClient.getIns |
