diff options
author | Aaron <51387595+AzureAaron@users.noreply.github.com> | 2024-04-09 15:58:13 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-09 15:58:13 -0400 |
commit | b5775f7f9a8cc9c12cc5fa3ce136cdf37361a567 (patch) | |
tree | dfa6e10c1258c29fca833c33b31203467dcd5b6f | |
parent | af64b5f098d47caa5fabd5c1fb4f8edb9ae714b6 (diff) | |
parent | 37365dd77a5f706ae8b5fa60655482e407ef2193 (diff) | |
download | Skyblocker-b5775f7f9a8cc9c12cc5fa3ce136cdf37361a567.tar.gz Skyblocker-b5775f7f9a8cc9c12cc5fa3ce136cdf37361a567.tar.bz2 Skyblocker-b5775f7f9a8cc9c12cc5fa3ce136cdf37361a567.zip |
Merge pull request #634 from AzureAaron/animated-dye-colours
Custom Animated Dyes
7 files changed, 203 insertions, 1 deletions
diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java index 639b340f..485a2103 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerMod.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerMod.java @@ -148,6 +148,7 @@ public class SkyblockerMod implements ClientModInitializer { TeleportOverlay.init(); CustomItemNames.init(); CustomArmorDyeColors.init(); + CustomArmorAnimatedDyes.init(); CustomArmorTrims.init(); TicTacToe.init(); QuiverWarning.init(); diff --git a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java index 06ac748a..418cc4d1 100644 --- a/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/SkyblockerConfig.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.config; import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.skyblock.item.CustomArmorAnimatedDyes; import de.hysky.skyblocker.skyblock.item.CustomArmorTrims; import de.hysky.skyblocker.utils.chat.ChatFilterResult; import de.hysky.skyblocker.utils.waypoint.Waypoint; @@ -263,6 +264,9 @@ public class SkyblockerConfig { @SerialEntry public Object2ObjectOpenHashMap<String, CustomArmorTrims.ArmorTrimId> customArmorTrims = new Object2ObjectOpenHashMap<>(); + + @SerialEntry + public Object2ObjectOpenHashMap<String, CustomArmorAnimatedDyes.AnimatedDye> customAnimatedDyes = new Object2ObjectOpenHashMap<>(); } public static class TabHudConf { diff --git a/src/main/java/de/hysky/skyblocker/mixin/DyeableItemMixin.java b/src/main/java/de/hysky/skyblocker/mixin/DyeableItemMixin.java index e5697085..64f6a452 100644 --- a/src/main/java/de/hysky/skyblocker/mixin/DyeableItemMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixin/DyeableItemMixin.java @@ -2,6 +2,7 @@ package de.hysky.skyblocker.mixin; import com.llamalad7.mixinextras.injector.ModifyReturnValue; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.skyblock.item.CustomArmorAnimatedDyes; import de.hysky.skyblocker.utils.ItemUtils; import de.hysky.skyblocker.utils.Utils; import net.minecraft.item.DyeableItem; @@ -16,6 +17,10 @@ public interface DyeableItemMixin { if (Utils.isOnSkyblock()) { String itemUuid = ItemUtils.getItemUuid(stack); + if (SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(itemUuid)) { + return CustomArmorAnimatedDyes.animateColorTransition(SkyblockerConfigManager.get().general.customAnimatedDyes.get(itemUuid)); + } + return SkyblockerConfigManager.get().general.customDyeColors.getOrDefault(itemUuid, originalColor); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/Tips.java b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java index ad345527..c483555e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/Tips.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/Tips.java @@ -27,6 +27,7 @@ public class Tips { getTipFactory("skyblocker.tips.customItemNames", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom renameItem"), getTipFactory("skyblocker.tips.customArmorDyeColors", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom dyeColor"), getTipFactory("skyblocker.tips.customArmorTrims", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom armorTrim"), + getTipFactory("skyblocker.tips.customAnimatedDyes", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker custom animatedDye"), getTipFactory("skyblocker.tips.fancyTabExtraInfo"), getTipFactory("skyblocker.tips.helpCommand", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker help"), getTipFactory("skyblocker.tips.discordRichPresence", ClickEvent.Action.SUGGEST_COMMAND, "/skyblocker config"), diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java new file mode 100644 index 00000000..b011b2b0 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/CustomArmorAnimatedDyes.java @@ -0,0 +1,181 @@ +package de.hysky.skyblocker.skyblock.item; + +import static com.mojang.brigadier.arguments.StringArgumentType.getString; +import static com.mojang.brigadier.arguments.StringArgumentType.word; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +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.config.SkyblockerConfigManager; +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.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.DyeableItem; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; + +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; + + 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(), null, null, 0, false, 0)) + .then(argument("hex1", word()) + .then(argument("hex2", word()) + .then(argument("samples", IntegerArgumentType.integer(1)) + .then(argument("cycleBack", BoolArgumentType.bool()) + .executes(context -> customizeAnimatedDye(context.getSource(), getString(context, "hex1"), getString(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(), getString(context, "hex1"), getString(context, "hex2"), IntegerArgumentType.getInteger(context, "samples"), BoolArgumentType.getBool(context, "cycleBack"), IntegerArgumentType.getInteger(context, "tickDelay"))))))))))); + } + + private static int customizeAnimatedDye(FabricClientCommandSource source, String hex1, String hex2, int samples, boolean cycleBack, int tickDelay) { + if (hex1 != null && hex2 != null && (!CustomArmorDyeColors.isHexadecimalColor(hex1) || !CustomArmorDyeColors.isHexadecimalColor(hex2))) { + source.sendError(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.invalidHex"))); + + return Command.SINGLE_SUCCESS; + } + + ItemStack heldItem = source.getPlayer().getMainHandStack(); + + if (Utils.isOnSkyblock() && heldItem != null && !heldItem.isEmpty()) { + if (heldItem.getItem() instanceof DyeableItem) { + String itemUuid = ItemUtils.getItemUuid(heldItem); + + if (!itemUuid.isEmpty()) { + Object2ObjectOpenHashMap<String, AnimatedDye> customAnimatedDyes = SkyblockerConfigManager.get().general.customAnimatedDyes; + + if (hex1 == null && hex2 == null) { + 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(Integer.decode("0x" + hex1.replace("#", "")), Integer.decode("0x" + hex2.replace("#", "")), 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); + } + + //Credit to https://codepen.io/OliverBalfour/post/programmatically-making-gradients + private static int interpolate(int firstColor, int secondColor, double percentage) { + int r1 = MathHelper.square((firstColor >> 16) & 0xFF); + int g1 = MathHelper.square((firstColor >> 8) & 0xFF); + int b1 = MathHelper.square(firstColor & 0xFF); + + int r2 = MathHelper.square((secondColor >> 16) & 0xFF); + int g2 = MathHelper.square((secondColor >> 8) & 0xFF); + int b2 = MathHelper.square(secondColor & 0xFF); + + double inverse = 1d - percentage; + + int r3 = (int) Math.floor(Math.sqrt(r1 * inverse + r2 * percentage)); + int g3 = (int) Math.floor(Math.sqrt(g1 * inverse + g2 * percentage)); + int b3 = (int) Math.floor(Math.sqrt(b1 * inverse + b2 * percentage)); + + return (r3 << 16) | (g3 << 8 ) | b3; + } + + 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) { + double percent = (1d / (double) 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 = CustomArmorAnimatedDyes.interpolate(color1, color2, percent); + stateTracker.lastColor = interpolatedColor; + + return interpolatedColor; + } + + //This will only happen if cycleBack is false + if (stateTracker.sampleCounter == samples) stateTracker.sampleCounter = 0; + + double percent = (1d / (double) samples) * stateTracker.getAndIncrement(); + int interpolatedColor = CustomArmorAnimatedDyes.interpolate(color1, color2, percent); + + stateTracker.lastColor = interpolatedColor; + + return interpolatedColor; + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java index 637aea22..62c50735 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/ItemTooltip.java @@ -218,7 +218,8 @@ public class ItemTooltip { } if (TooltipInfoType.COLOR.isTooltipEnabledAndHasOrNullWarning(internalID) && stack.getNbt() != null) { - boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(ItemUtils.getItemUuid(stack)); + String uuid = ItemUtils.getItemUuid(stack); + boolean hasCustomDye = SkyblockerConfigManager.get().general.customDyeColors.containsKey(uuid) || SkyblockerConfigManager.get().general.customAnimatedDyes.containsKey(uuid); if (!hasCustomDye && stack.getItem() instanceof DyeableItem item && item.hasColor(stack)) { String colorHex = String.format("%06X", item.getColor(stack)); diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index 84409b9f..c3b8c92e 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -553,6 +553,14 @@ "skyblocker.customArmorTrims.added": "§fSet a custom armor trim for your currently held item!", "skyblocker.customArmorTrims.noItemUuid": "§cYou must be holding an item that has a uuid in order to set a custom armor trim!", "skyblocker.customArmorTrims.unableToSetTrim": "§cUnable to set a custom armor trim :( (Are you in skyblock?, are you holding an item?)", + + "skyblocker.customAnimatedDyes.invalidHex": "§cAn invalid HEX color code was supplied!", + "skyblocker.customAnimatedDyes.notDyeable": "§cThis item isn't a dyeable armor piece!", + "skyblocker.customAnimatedDyes.removed": "Removed this item's custom animated dye.", + "skyblocker.customAnimatedDyes.neverHad": "This item doesn't have a custom animated dye set, but why not add one? ;)", + "skyblocker.customAnimatedDyes.added": "Set a custom animated dye for your currently held item!", + "skyblocker.customAnimatedDyes.noItemUuid": "§cYou must be holding an item that has a uuid in order to set a custom animated dye.", + "skyblocker.customAnimatedDyes.unableToSetDye": "§cUnable to set a custom animated dye :( (Are you in skyblock?, are you holding an item?)", "skyblocker.quiverWarning.50Left": "You only have 50 Arrows left in your Quiver!", "skyblocker.quiverWarning.10Left": "You only have 10 Arrows left in your Quiver!", @@ -572,6 +580,7 @@ "skyblocker.tips.customItemNames": "Customize the names of your items with /skyblocker custom renameItem", "skyblocker.tips.customArmorDyeColors": "Apply a custom dye color to your leather armour with /skyblocker custom dyeColor", "skyblocker.tips.customArmorTrims": "You can set custom armor trims on your armor using /skyblocker custom armorTrim.", + "skyblocker.tips.customAnimatedDyes": "You can apply a custom animated dye to your leather armour with /skyblocker custom animatedDye!", "skyblocker.tips.fancyTabExtraInfo": "Did you know you can see extra info on our fancy tab menu when holding N or M?", "skyblocker.tips.helpCommand": "Use command /skyblocker help and you might find some more nifty features!", "skyblocker.tips.discordRichPresence": "Use Discord Rich Presence to show your friends how loaded you are!", |