From 4c822a6383d8f92dc850c82b93fa6cd4a67f2610 Mon Sep 17 00:00:00 2001 From: viciscat <51047087+viciscat@users.noreply.github.com> Date: Sun, 1 Jun 2025 02:25:16 +0200 Subject: 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> --- .../skyblocker/config/SkyblockerConfigManager.java | 2 +- .../skyblocker/config/configs/GeneralConfig.java | 4 +- .../config/datafixer/ConfigDataFixer.java | 2 + .../ConfigFix3AnimatedDyeAndItemBackground.java | 54 +++ src/main/java/de/hysky/skyblocker/debug/Debug.java | 4 +- .../skyblocker/mixins/ComponentHolderMixin.java | 2 +- .../skyblocker/mixins/DyedColorComponentMixin.java | 2 +- .../accessors/EntityRenderDispatcherAccessor.java | 12 + .../mixins/accessors/SpriteContentsAccessor.java | 12 + .../skyblock/item/CustomArmorAnimatedDyes.java | 156 --------- .../skyblock/item/CustomArmorDyeColors.java | 72 ---- .../skyblocker/skyblock/item/CustomArmorTrims.java | 158 --------- .../skyblocker/skyblock/item/CustomItemNames.java | 72 ---- .../item/custom/CustomArmorAnimatedDyes.java | 180 ++++++++++ .../skyblock/item/custom/CustomArmorDyeColors.java | 70 ++++ .../skyblock/item/custom/CustomArmorTrims.java | 150 +++++++++ .../skyblock/item/custom/CustomItemNames.java | 72 ++++ .../custom/screen/AnimatedDyeTimelineWidget.java | 254 ++++++++++++++ .../item/custom/screen/ColorSelectionWidget.java | 366 +++++++++++++++++++++ .../item/custom/screen/CustomizeArmorScreen.java | 276 ++++++++++++++++ .../skyblock/item/custom/screen/PlayerWidget.java | 71 ++++ .../item/custom/screen/TrimElementButton.java | 210 ++++++++++++ .../item/custom/screen/TrimSelectionWidget.java | 196 +++++++++++ .../skyblock/item/tooltip/BackpackPreview.java | 3 +- .../java/de/hysky/skyblocker/utils/ItemUtils.java | 3 +- .../java/de/hysky/skyblocker/utils/OkLabColor.java | 1 + src/main/java/de/hysky/skyblocker/utils/Utils.java | 14 +- .../datafixer/ItemStackComponentizationFixer.java | 18 +- .../utils/datafixer/LegacyItemStackFixer.java | 3 +- .../skyblocker/utils/render/RenderHelper.java | 11 + .../utils/render/gui/ColorPickerWidget.java | 186 +++++++++++ .../skyblocker/utils/render/gui/RGBTextInput.java | 218 ++++++++++++ 32 files changed, 2369 insertions(+), 485 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/config/datafixer/ConfigFix3AnimatedDyeAndItemBackground.java create mode 100644 src/main/java/de/hysky/skyblocker/mixins/accessors/EntityRenderDispatcherAccessor.java create mode 100644 src/main/java/de/hysky/skyblocker/mixins/accessors/SpriteContentsAccessor.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java delete mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorAnimatedDyes.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorDyeColors.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorTrims.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomItemNames.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/AnimatedDyeTimelineWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/ColorSelectionWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/CustomizeArmorScreen.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/PlayerWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/TrimElementButton.java create mode 100644 src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/TrimSelectionWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/utils/render/gui/ColorPickerWidget.java create mode 100644 src/main/java/de/hysky/skyblocker/utils/render/gui/RGBTextInput.java (limited to 'src/main/java/de') 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 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 Dynamic fix(Dynamic 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 Dynamic fixCustomAnimatedDye(Dynamic 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 STATE_TRACKER_MAP = new Object2ObjectOpenHashMap<>(); - private static final Object2ObjectFunction 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 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 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/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java deleted file mode 100644 index 52b6f1ae..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorDyeColors.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.hysky.skyblocker.skyblock.item; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -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.Utils; -import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.item.ItemStack; -import net.minecraft.registry.tag.ItemTags; -import net.minecraft.text.Text; - -public class CustomArmorDyeColors { - @Init - public static void init() { - ClientCommandRegistrationCallback.EVENT.register(CustomArmorDyeColors::registerCommands); - } - - private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(ClientCommandManager.literal("skyblocker") - .then(ClientCommandManager.literal("custom") - .then(ClientCommandManager.literal("dyeColor") - .executes(context -> customizeDyeColor(context.getSource(), Integer.MIN_VALUE)) - .then(ClientCommandManager.argument("hexCode", ColorArgumentType.hex()) - .executes(context -> customizeDyeColor(context.getSource(), ColorArgumentType.getIntFromHex(context, "hexCode"))))))); - } - - @SuppressWarnings("SameReturnValue") - private static int customizeDyeColor(FabricClientCommandSource source, int color) { - ItemStack heldItem = source.getPlayer().getMainHandStack(); - - if (Utils.isOnSkyblock() && heldItem != null) { - if (heldItem.isIn(ItemTags.DYEABLE)) { - String itemUuid = ItemUtils.getItemUuid(heldItem); - - if (!itemUuid.isEmpty()) { - Object2IntOpenHashMap customDyeColors = SkyblockerConfigManager.get().general.customDyeColors; - - if (color == Integer.MIN_VALUE) { - if (customDyeColors.containsKey(itemUuid)) { - customDyeColors.removeInt(itemUuid); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.removed"))); - } else { - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.neverHad"))); - } - } else { - customDyeColors.put(itemUuid, color); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.noItemUuid"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.notDyeable"))); - return Command.SINGLE_SUCCESS; - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.unableToSetColor"))); - } - - return Command.SINGLE_SUCCESS; - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java deleted file mode 100644 index f8dfb7bd..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorTrims.java +++ /dev/null @@ -1,158 +0,0 @@ -package de.hysky.skyblocker.skyblock.item; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import de.hysky.skyblocker.annotations.Init; -import de.hysky.skyblocker.config.SkyblockerConfigManager; -import de.hysky.skyblocker.debug.Debug; -import de.hysky.skyblocker.events.SkyblockEvents; -import de.hysky.skyblocker.utils.Constants; -import de.hysky.skyblocker.utils.ItemUtils; -import de.hysky.skyblocker.utils.Utils; -import dev.isxander.yacl3.config.v2.api.SerialEntry; -import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.CommandSource; -import net.minecraft.command.argument.IdentifierArgumentType; -import net.minecraft.item.ItemStack; -import net.minecraft.item.equipment.trim.ArmorTrim; -import net.minecraft.item.equipment.trim.ArmorTrimMaterial; -import net.minecraft.item.equipment.trim.ArmorTrimPattern; -import net.minecraft.registry.*; -import net.minecraft.registry.entry.RegistryEntry.Reference; -import net.minecraft.registry.tag.ItemTags; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CustomArmorTrims { - private static final Logger LOGGER = LoggerFactory.getLogger(CustomArmorTrims.class); - public static final Object2ObjectOpenHashMap TRIMS_CACHE = new Object2ObjectOpenHashMap<>(); - private static boolean trimsInitialized = false; - - @Init - public static void init() { - SkyblockEvents.JOIN.register(CustomArmorTrims::initializeTrimCache); - ClientCommandRegistrationCallback.EVENT.register(CustomArmorTrims::registerCommand); - } - - private static void initializeTrimCache() { - MinecraftClient client = MinecraftClient.getInstance(); - FabricLoader loader = FabricLoader.getInstance(); - if (trimsInitialized || (client == null && !Debug.debugEnabled())) { - return; - } - try { - TRIMS_CACHE.clear(); - RegistryWrapper.WrapperLookup wrapperLookup = getWrapperLookup(loader, client); - for (Reference material : wrapperLookup.getOrThrow(RegistryKeys.TRIM_MATERIAL).streamEntries().toList()) { - for (Reference pattern : wrapperLookup.getOrThrow(RegistryKeys.TRIM_PATTERN).streamEntries().toList()) { - ArmorTrim trim = new ArmorTrim(material, pattern); - - TRIMS_CACHE.put(new ArmorTrimId(material.registryKey().getValue(), pattern.registryKey().getValue()), trim); - } - } - - LOGGER.info("[Skyblocker] Successfully cached all armor trims!"); - trimsInitialized = true; - } catch (Exception e) { - LOGGER.error("[Skyblocker] Encountered an exception while caching armor trims", e); - } - } - - private static RegistryWrapper.WrapperLookup getWrapperLookup(FabricLoader loader, MinecraftClient client) { - return client != null && client.getNetworkHandler() != null && client.getNetworkHandler().getRegistryManager() != null ? client.getNetworkHandler().getRegistryManager() : BuiltinRegistries.createWrapperLookup(); - } - - private static void registerCommand(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(ClientCommandManager.literal("skyblocker") - .then(ClientCommandManager.literal("custom") - .then(ClientCommandManager.literal("armorTrim") - .executes(context -> customizeTrim(context.getSource(), null, null)) - .then(ClientCommandManager.argument("material", IdentifierArgumentType.identifier()) - .suggests(getIdSuggestionProvider(RegistryKeys.TRIM_MATERIAL)) - .executes(context -> customizeTrim(context.getSource(), context.getArgument("material", Identifier.class), null)) - .then(ClientCommandManager.argument("pattern", IdentifierArgumentType.identifier()) - .suggests(getIdSuggestionProvider(RegistryKeys.TRIM_PATTERN)) - .executes(context -> customizeTrim(context.getSource(), context.getArgument("material", Identifier.class), context.getArgument("pattern", Identifier.class)))))))); - } - - @NotNull - private static SuggestionProvider getIdSuggestionProvider(RegistryKey> registryKey) { - return (context, builder) -> context.getSource().listIdSuggestions(registryKey, CommandSource.SuggestedIdType.ELEMENTS, builder, context); - } - - @SuppressWarnings("SameReturnValue") - private static int customizeTrim(FabricClientCommandSource source, Identifier material, Identifier pattern) { - ItemStack heldItem = source.getPlayer().getMainHandStack(); - - if (Utils.isOnSkyblock() && heldItem != null) { - if (heldItem.isIn(ItemTags.TRIMMABLE_ARMOR)) { - String itemUuid = ItemUtils.getItemUuid(heldItem); - - if (!itemUuid.isEmpty()) { - Object2ObjectOpenHashMap customArmorTrims = SkyblockerConfigManager.get().general.customArmorTrims; - - if (material == null && pattern == null) { - if (customArmorTrims.containsKey(itemUuid)) { - customArmorTrims.remove(itemUuid); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.removed"))); - } else { - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.neverHad"))); - } - } else { - // Ensure that the material & trim are valid - ArmorTrimId trimId = new ArmorTrimId(material, pattern); - if (TRIMS_CACHE.get(trimId) == null) { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern"))); - - return Command.SINGLE_SUCCESS; - } - - customArmorTrims.put(itemUuid, trimId); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.added"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.noItemUuid"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece"))); - return Command.SINGLE_SUCCESS; - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim"))); - } - - return Command.SINGLE_SUCCESS; - } - - public record ArmorTrimId(@SerialEntry Identifier material, @SerialEntry Identifier pattern) implements Pair { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material), - Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern)) - .apply(instance, ArmorTrimId::new)); - - @Override - public Identifier left() { - return material(); - } - - @Override - public Identifier right() { - return pattern(); - } - } -} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java deleted file mode 100644 index a06307e9..00000000 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomItemNames.java +++ /dev/null @@ -1,72 +0,0 @@ -package de.hysky.skyblocker.skyblock.item; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -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.Utils; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.argument.TextArgumentType; -import net.minecraft.text.MutableText; -import net.minecraft.text.Style; -import net.minecraft.text.Text; - -public class CustomItemNames { - @Init - public static void init() { - ClientCommandRegistrationCallback.EVENT.register(CustomItemNames::registerCommands); - } - - private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(ClientCommandManager.literal("skyblocker") - .then(ClientCommandManager.literal("custom") - .then(ClientCommandManager.literal("renameItem") - .executes(context -> renameItem(context.getSource(), null)) - .then(ClientCommandManager.argument("textComponent", TextArgumentType.text(registryAccess)) - .executes(context -> renameItem(context.getSource(), context.getArgument("textComponent", Text.class))))))); - } - - @SuppressWarnings("SameReturnValue") - private static int renameItem(FabricClientCommandSource source, Text text) { - if (Utils.isOnSkyblock()) { - String itemUuid = ItemUtils.getItemUuid(source.getPlayer().getMainHandStack()); - - if (!itemUuid.isEmpty()) { - Object2ObjectOpenHashMap customItemNames = SkyblockerConfigManager.get().general.customItemNames; - - if (text == null) { - if (customItemNames.containsKey(itemUuid)) { - //Remove custom item name when the text argument isn't passed - customItemNames.remove(itemUuid); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.removed"))); - } else { - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.neverHad"))); - } - } else { - //If the text is provided then set the item's custom name to it - - //Set italic to false if it hasn't been changed (or was already false) - Style currentStyle = text.getStyle(); - ((MutableText) text).setStyle(currentStyle.withItalic(currentStyle.isItalic())); - - customItemNames.put(itemUuid, text); - SkyblockerConfigManager.save(); - source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.added"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.noItemUuid"))); - } - } else { - source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.unableToSetName"))); - } - - return Command.SINGLE_SUCCESS; - } -} 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 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 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 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.getInstance().getRenderTickCounter().getDynamicDeltaTicks()); + } + + @VisibleForTesting + static class AnimatedDyeStateTracker { + private float progress = 0; + private boolean onBackCycle = false; + private int lastColor = 0; + private int lastRecordedFrame = 0; + + @VisibleForTesting + AnimatedDyeStateTracker(AnimatedDye animatedDye) { + if (animatedDye.delay() > 0) { + if (animatedDye.cycleBack()) { + onBackCycle = true; + progress = animatedDye.delay() / animatedDye.duration(); + } else { + progress = 1 - animatedDye.delay() / animatedDye.duration(); + } + progress = clamp(progress); + } + } + + @VisibleForTesting + int interpolate(AnimatedDye animatedDye, float deltaTicks) { + update(animatedDye, deltaTicks); + + int keyframe = 0; + // keyframe cannot be the last keyframe, or else keyframe + 1 will be out of bounds, so we check for less than size - 2 + while (keyframe < animatedDye.keyframes.size() - 2 && animatedDye.keyframes.get(keyframe + 1).time < progress) keyframe++; + + Keyframe current = onBackCycle ? animatedDye.keyframes.get(keyframe + 1) : animatedDye.keyframes.get(keyframe); + Keyframe next = onBackCycle ? animatedDye.keyframes.get(keyframe) : animatedDye.keyframes.get(keyframe + 1); + + float colorProgress = (progress - current.time) / (next.time - current.time); + colorProgress = clamp(colorProgress); + + return lastColor = OkLabColor.interpolate(current.color, next.color, colorProgress); + } + + private void update(AnimatedDye animatedDye, float deltaTicks) { + float v = deltaTicks * 0.05f / animatedDye.duration; + if (onBackCycle) { + progress -= v; + if (progress <= 0f) { + onBackCycle = false; + progress = Math.abs(progress); + } + } else { + progress += v; + if (progress >= 1f) { + if (animatedDye.cycleBack) { + onBackCycle = true; + progress = 2f - progress; + } else { + progress %= 1f; + } + } + } + + // Sanity clamp because I got some pretty weird errors with progress being greater than 1 + progress = clamp(progress); + } + + private static float clamp(float progress) { + return Math.clamp(progress, 0, 1); + } + } + + public static void cleanTrackers() { + STATE_TRACKER_MAP.clear(); + } + + public record Keyframe(@SerialEntry int color, @SerialEntry float time) {} + public record AnimatedDye(@SerialEntry List keyframes, @SerialEntry boolean cycleBack, @SerialEntry float delay, @SerialEntry float duration) {} +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorDyeColors.java b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorDyeColors.java new file mode 100644 index 00000000..6483b3a5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorDyeColors.java @@ -0,0 +1,70 @@ +package de.hysky.skyblocker.skyblock.item.custom; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +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.Utils; +import de.hysky.skyblocker.utils.command.argumenttypes.color.ColorArgumentType; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.text.Text; + +public class CustomArmorDyeColors { + @Init + public static void init() { + ClientCommandRegistrationCallback.EVENT.register(CustomArmorDyeColors::registerCommands); + } + + private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(ClientCommandManager.literal("skyblocker") + .then(ClientCommandManager.literal("custom") + .then(ClientCommandManager.literal("dyeColor") + .executes(context -> customizeDyeColor(context.getSource(), Integer.MIN_VALUE)) + .then(ClientCommandManager.argument("hexCode", ColorArgumentType.hex()) + .executes(context -> customizeDyeColor(context.getSource(), ColorArgumentType.getIntFromHex(context, "hexCode"))))))); + } + + @SuppressWarnings("SameReturnValue") + private static int customizeDyeColor(FabricClientCommandSource source, int color) { + ItemStack heldItem = source.getPlayer().getMainHandStack(); + + if (Utils.isOnSkyblock() && heldItem != null) { + if (heldItem.isIn(ItemTags.DYEABLE)) { + String itemUuid = ItemUtils.getItemUuid(heldItem); + + if (!itemUuid.isEmpty()) { + Object2IntOpenHashMap customDyeColors = SkyblockerConfigManager.get().general.customDyeColors; + + if (color == Integer.MIN_VALUE) { + if (customDyeColors.containsKey(itemUuid)) { + SkyblockerConfigManager.update(config-> config.general.customDyeColors.removeInt(itemUuid)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.removed"))); + } else { + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.neverHad"))); + } + } else { + SkyblockerConfigManager.update(config-> config.general.customDyeColors.put(itemUuid, color)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.noItemUuid"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.notDyeable"))); + return Command.SINGLE_SUCCESS; + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.unableToSetColor"))); + } + + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorTrims.java b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorTrims.java new file mode 100644 index 00000000..f6a7cbb3 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomArmorTrims.java @@ -0,0 +1,150 @@ +package de.hysky.skyblocker.skyblock.item.custom; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.debug.Debug; +import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.Constants; +import de.hysky.skyblocker.utils.ItemUtils; +import de.hysky.skyblocker.utils.Utils; +import dev.isxander.yacl3.config.v2.api.SerialEntry; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +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.command.CommandRegistryAccess; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.IdentifierArgumentType; +import net.minecraft.item.ItemStack; +import net.minecraft.item.equipment.trim.ArmorTrim; +import net.minecraft.item.equipment.trim.ArmorTrimMaterial; +import net.minecraft.item.equipment.trim.ArmorTrimPattern; +import net.minecraft.registry.*; +import net.minecraft.registry.entry.RegistryEntry.Reference; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CustomArmorTrims { + private static final Logger LOGGER = LoggerFactory.getLogger(CustomArmorTrims.class); + public static final Object2ObjectOpenHashMap TRIMS_CACHE = new Object2ObjectOpenHashMap<>(); + private static boolean trimsInitialized = false; + + @Init + public static void init() { + SkyblockEvents.JOIN.register(CustomArmorTrims::initializeTrimCache); + ClientCommandRegistrationCallback.EVENT.register(CustomArmorTrims::registerCommand); + } + + private static void initializeTrimCache() { + MinecraftClient client = MinecraftClient.getInstance(); + if (trimsInitialized || (client == null && !Debug.debugEnabled())) { + return; + } + try { + TRIMS_CACHE.clear(); + RegistryWrapper.WrapperLookup wrapperLookup = Utils.getRegistryWrapperLookup(); + for (Reference material : wrapperLookup.getOrThrow(RegistryKeys.TRIM_MATERIAL).streamEntries().toList()) { + for (Reference pattern : wrapperLookup.getOrThrow(RegistryKeys.TRIM_PATTERN).streamEntries().toList()) { + ArmorTrim trim = new ArmorTrim(material, pattern); + + TRIMS_CACHE.put(new ArmorTrimId(material.registryKey().getValue(), pattern.registryKey().getValue()), trim); + } + } + + LOGGER.info("[Skyblocker] Successfully cached all armor trims!"); + trimsInitialized = true; + } catch (Exception e) { + LOGGER.error("[Skyblocker] Encountered an exception while caching armor trims", e); + } + } + + private static void registerCommand(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(ClientCommandManager.literal("skyblocker") + .then(ClientCommandManager.literal("custom") + .then(ClientCommandManager.literal("armorTrim") + .executes(context -> customizeTrim(context.getSource(), null, null)) + .then(ClientCommandManager.argument("material", IdentifierArgumentType.identifier()) + .suggests(getIdSuggestionProvider(RegistryKeys.TRIM_MATERIAL)) + .executes(context -> customizeTrim(context.getSource(), context.getArgument("material", Identifier.class), null)) + .then(ClientCommandManager.argument("pattern", IdentifierArgumentType.identifier()) + .suggests(getIdSuggestionProvider(RegistryKeys.TRIM_PATTERN)) + .executes(context -> customizeTrim(context.getSource(), context.getArgument("material", Identifier.class), context.getArgument("pattern", Identifier.class)))))))); + } + + @NotNull + private static SuggestionProvider getIdSuggestionProvider(RegistryKey> registryKey) { + return (context, builder) -> context.getSource().listIdSuggestions(registryKey, CommandSource.SuggestedIdType.ELEMENTS, builder, context); + } + + @SuppressWarnings("SameReturnValue") + private static int customizeTrim(FabricClientCommandSource source, Identifier material, Identifier pattern) { + ItemStack heldItem = source.getPlayer().getMainHandStack(); + + if (Utils.isOnSkyblock() && heldItem != null) { + if (heldItem.isIn(ItemTags.TRIMMABLE_ARMOR)) { + String itemUuid = ItemUtils.getItemUuid(heldItem); + + if (!itemUuid.isEmpty()) { + Object2ObjectOpenHashMap customArmorTrims = SkyblockerConfigManager.get().general.customArmorTrims; + + if (material == null && pattern == null) { + if (customArmorTrims.containsKey(itemUuid)) { + SkyblockerConfigManager.update(config -> config.general.customArmorTrims.remove(itemUuid)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.removed"))); + } else { + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.neverHad"))); + } + } else { + // Ensure that the material & trim are valid + ArmorTrimId trimId = new ArmorTrimId(material, pattern); + if (TRIMS_CACHE.get(trimId) == null) { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.invalidMaterialOrPattern"))); + + return Command.SINGLE_SUCCESS; + } + + SkyblockerConfigManager.update(config -> config.general.customArmorTrims.put(itemUuid, trimId)); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.added"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.noItemUuid"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.notAnArmorPiece"))); + return Command.SINGLE_SUCCESS; + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.unableToSetTrim"))); + } + + return Command.SINGLE_SUCCESS; + } + + public record ArmorTrimId(@SerialEntry Identifier material, @SerialEntry Identifier pattern) implements Pair { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("material").forGetter(ArmorTrimId::material), + Identifier.CODEC.fieldOf("pattern").forGetter(ArmorTrimId::pattern)) + .apply(instance, ArmorTrimId::new)); + + @Override + public Identifier left() { + return material(); + } + + @Override + public Identifier right() { + return pattern(); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomItemNames.java b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomItemNames.java new file mode 100644 index 00000000..23ff68e5 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/CustomItemNames.java @@ -0,0 +1,72 @@ +package de.hysky.skyblocker.skyblock.item.custom; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +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.Utils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.argument.TextArgumentType; +import net.minecraft.text.MutableText; +import net.minecraft.text.Style; +import net.minecraft.text.Text; + +public class CustomItemNames { + @Init + public static void init() { + ClientCommandRegistrationCallback.EVENT.register(CustomItemNames::registerCommands); + } + + private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { + dispatcher.register(ClientCommandManager.literal("skyblocker") + .then(ClientCommandManager.literal("custom") + .then(ClientCommandManager.literal("renameItem") + .executes(context -> renameItem(context.getSource(), null)) + .then(ClientCommandManager.argument("textComponent", TextArgumentType.text(registryAccess)) + .executes(context -> renameItem(context.getSource(), context.getArgument("textComponent", Text.class))))))); + } + + @SuppressWarnings("SameReturnValue") + private static int renameItem(FabricClientCommandSource source, Text text) { + if (Utils.isOnSkyblock()) { + String itemUuid = ItemUtils.getItemUuid(source.getPlayer().getMainHandStack()); + + if (!itemUuid.isEmpty()) { + Object2ObjectOpenHashMap customItemNames = SkyblockerConfigManager.get().general.customItemNames; + + if (text == null) { + if (customItemNames.containsKey(itemUuid)) { + //Remove custom item name when the text argument isn't passed + customItemNames.remove(itemUuid); + SkyblockerConfigManager.save(); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.removed"))); + } else { + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.neverHad"))); + } + } else { + //If the text is provided then set the item's custom name to it + + //Set italic to false if it hasn't been changed (or was already false) + Style currentStyle = text.getStyle(); + ((MutableText) text).setStyle(currentStyle.withItalic(currentStyle.isItalic())); + + customItemNames.put(itemUuid, text); + SkyblockerConfigManager.save(); + source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.added"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.noItemUuid"))); + } + } else { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customItemNames.unableToSetName"))); + } + + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/AnimatedDyeTimelineWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/AnimatedDyeTimelineWidget.java new file mode 100644 index 00000000..19968f92 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/custom/screen/AnimatedDyeTimelineWidget.java @@ -0,0 +1,254 @@ +package de.hysky.skyblocker.skyblock.item.custom.screen; + +import com.google.common.collect.ImmutableList; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.custom.CustomArmorAnimatedDyes; +import de.hysky.skyblocker.utils.OkLabColor; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.widget.ContainerWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; +import net.minecraft.text.Text; +import net.minecraft.util.Colors; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class AnimatedDyeTimelineWidget extends ContainerWidget implements Closeable { + + private static final Identifier GRADIENT_TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "generated/dye_gradient"); + + private static final int HORIZONTAL_MARGIN = 3; + private static final int VERTICAL_MARGIN = 1; + + private final NativeImageBackedTexture gradientTexture; + private final int textureWidth; + private final int textureHeight; + private final FrameCallback frameCallback; + + private String uuid = ""; + + private final ArrayList keyframes = new ArrayList<>(); + private @Nullable AnimatedDyeTimelineWidget.KeyframeWidget focusedFrame = null; + + public AnimatedDyeTimelineWidget(int x, int y, int width, int height, FrameCallback frameCallback) { + super(x, y, width, height, Text.literal("Animated Dye Timeline")); + gradientTexture = new NativeImageBackedTexture("TimelineGradient", width - HORIZONTAL_MARGIN * 2, height - VERTICAL_MARGIN * 2, true); + assert gradientTexture.getImage() != null; + textureWidth = gradientTexture.getImage().getWidth(); + textureHeight = gradientTexture.getImage().getHeight(); + MinecraftClient.getInstance().getTextureManager().registerTexture(GRADIENT_TEXTURE, gradientTexture); + this.frameCallback = frameCallback; + } + + @Override + public List children() { + return keyframes; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.drawTexture(RenderLayer::getGuiTextured, + GRADIENT_TEXTURE, + getX() + HORIZONTAL_MARGIN, + getY() + VERTICAL_MARGIN, + 0, 0, + getWidth() - HORIZONTAL_MARGIN * 2, + getHeight() - VERTICAL_MARGIN * 2, + textureWidth, textureHeight, + textureWidth, textureHeight + ); + for (KeyframeWidget frame : keyframes) { + frame.render(context, mouseX, mouseY, delta); + } + } + + @Override + public void setFocused(@Nullable Element focused) { + super.setFocused(focused); + if (focused instanceof KeyframeWidget keyframe) { + frameCallback.onFrameSelected(keyframe.color, keyframe.time); + focusedFrame = keyframe; + } + } + + public void setAnimatedDye(String uuid) { + this.uuid = uuid; + CustomArmorAnimatedDyes.AnimatedDye dye = SkyblockerConfigManager.get().general.customAnimatedDyes.get(uuid); + keyframes.clear(); + keyframes.ensureCapacity(dye.keyframes().size()); + for (int i = 0; i < dye.keyframes().size(); i++) { + CustomArmorAnimatedDyes.Keyframe keyframe = dye.keyframes().get(i); + keyframes.add(new KeyframeWidget(keyframe.color(), keyframe.time(), i != 0 && i != dye.keyframes().size() - 1)); +