aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorviciscat <51047087+viciscat@users.noreply.github.com>2025-06-01 02:25:16 +0200
committerGitHub <noreply@github.com>2025-06-01 08:25:16 +0800
commit4c822a6383d8f92dc850c82b93fa6cd4a67f2610 (patch)
tree714dcfd9eaa66da04546832f2a27574843d280d3
parent86f36d661f551f8639b30fb39312b6b9290b8a02 (diff)
downloadSkyblocker-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>
-rw-r--r--src/main/java/de/hysky/skyblocker/config/SkyblockerConfigManager.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/configs/GeneralConfig.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixer.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix3AnimatedDyeAndItemBackground.java54
-rw-r--r--src/main/java/de/hysky/skyblocker/debug/Debug.java4
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/ComponentHolderMixin.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java2
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/EntityRenderDispatcherAccessor.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/mixins/accessors/SpriteContentsAccessor.java12
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java156
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyes.java180
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorDyeColors.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java)8
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorTrims.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java)16
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomItemNames.java (renamed from src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java)2
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/AnimatedDyeTimelineWidget.java254
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/ColorSelectionWidget.java366
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/CustomizeArmorScreen.java276
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/PlayerWidget.java71
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/TrimElementButton.java210
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/TrimSelectionWidget.java196
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/BackpackPreview.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/ItemUtils.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/OkLabColor.java1
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/Utils.java14
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixer.java18
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/datafixer/LegacyItemStackFixer.java3
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java11
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/ColorPickerWidget.java186
-rw-r--r--src/main/java/de/hysky/skyblocker/utils/render/gui/RGBTextInput.java218
-rw-r--r--src/main/resources/assets/skyblocker/lang/en_us.json11
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/armor_customization_screen/button.pngbin0 -> 8999 bytes
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/armor_customization_screen/mini_hotbar.pngbin0 -> 2344 bytes
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/color_picker/sv_thumb.pngbin0 -> 157 bytes
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/hotbar_selection_full.pngbin0 -> 720 bytes
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/menu_inner_space.pngbin0 -> 173 bytes
-rw-r--r--src/main/resources/assets/skyblocker/textures/gui/sprites/menu_inner_space.png.mcmeta10
-rw-r--r--src/main/resources/skyblocker.mixins.json2
-rw-r--r--src/test/java/de/hysky/skyblocker/config/datafixer/ConfigDataFixerTest.java12
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/item/ArmorTrimIdSerializationTest.java2
-rw-r--r--src/test/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyesTest.java25
-rw-r--r--src/test/java/de/hysky/skyblocker/utils/datafixer/ItemStackComponentizationFixerTest.java5
-rw-r--r--src/test/resources/assets/skyblocker/config/skyblocker-v4.json580
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