Skip to content

Commit

Permalink
Merge pull request #634 from AzureAaron/animated-dye-colours
Browse files Browse the repository at this point in the history
Custom Animated Dyes
  • Loading branch information
AzureAaron authored Apr 9, 2024
2 parents af64b5f + 37365dd commit b5775f7
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/main/java/de/hysky/skyblocker/SkyblockerMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public void onInitializeClient() {
TeleportOverlay.init();
CustomItemNames.init();
CustomArmorDyeColors.init();
CustomArmorAnimatedDyes.init();
CustomArmorTrims.init();
TicTacToe.init();
QuiverWarning.init();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -263,6 +264,9 @@ public static class General {

@SerialEntry
public Object2ObjectOpenHashMap<String, CustomArmorTrims.ArmorTrimId> customArmorTrims = new Object2ObjectOpenHashMap<>();

@SerialEntry
public Object2ObjectOpenHashMap<String, CustomArmorAnimatedDyes.AnimatedDye> customAnimatedDyes = new Object2ObjectOpenHashMap<>();
}

public static class TabHudConf {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/de/hysky/skyblocker/mixin/DyeableItemMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/de/hysky/skyblocker/skyblock/Tips.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ public static void getTooltip(ItemStack stack, TooltipContext context, List<Text
}

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));
Expand Down
9 changes: 9 additions & 0 deletions src/main/resources/assets/skyblocker/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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!",
Expand All @@ -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!",
Expand Down

0 comments on commit b5775f7

Please sign in to comment.