Skip to content

Epic Armor Customization GUI #1215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static void init() {
throw new RuntimeException("Skyblocker: Called config init from an illegal place!");
}

HANDLER.load();
HANDLER.load();
ClientCommandRegistrationCallback.EVENT.register(((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE).then(optionsLiteral("config")).then(optionsLiteral("options")))));
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
if (screen instanceof GenericContainerScreen genericContainerScreen && screen.getTitle().getString().equals("SkyBlock Menu")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
EquipmentModelLoader getEquipmentModelLoader();
}
Original file line number Diff line number Diff line change
@@ -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("image")
NativeImage getImage();
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,46 @@
package de.hysky.skyblocker.skyblock.item;
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.IntegerArgumentType;
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.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.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderTickCounter;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.text.Text;

import java.util.List;

import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;

public class CustomArmorAnimatedDyes {
private static final Object2ObjectOpenHashMap<AnimatedDye, AnimatedDyeStateTracker> STATE_TRACKER_MAP = new Object2ObjectOpenHashMap<>();
private static final Object2ObjectFunction<AnimatedDye, AnimatedDyeStateTracker> NEW_STATE_TRACKER = _dye -> AnimatedDyeStateTracker.create();
private static final int DEFAULT_TICK_DELAY = 4;
private static int ticks;
private static final float DEFAULT_DELAY = 0;
private static int frames;

@Init
public static void init() {
ClientCommandRegistrationCallback.EVENT.register(CustomArmorAnimatedDyes::registerCommands);
ClientTickEvents.END_CLIENT_TICK.register(_client -> ++ticks);
WorldRenderEvents.START.register(ignored -> ++frames);
// have the animation restart on world change because why not?
SkyblockEvents.LOCATION_CHANGE.register(ignored -> cleanTrackers());
}

private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
Expand All @@ -45,14 +50,14 @@ private static void registerCommands(CommandDispatcher<FabricClientCommandSource
.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("duration", FloatArgumentType.floatArg(0.1f, 10f))
.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")))))))))));
.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, int samples, boolean cycleBack, int tickDelay) {
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()) {
Expand All @@ -64,17 +69,15 @@ private static int customizeAnimatedDye(FabricClientCommandSource source, int co

if (color1 == Integer.MIN_VALUE && color2 == Integer.MIN_VALUE) {
if (customAnimatedDyes.containsKey(itemUuid)) {
customAnimatedDyes.remove(itemUuid);
SkyblockerConfigManager.save();
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(color1, color2, samples, cycleBack, tickDelay);
AnimatedDye animatedDye = new AnimatedDye(List.of(new DyeFrame(color1, 0), new DyeFrame(color2, 1)), cycleBack, delay, duration);

customAnimatedDyes.put(itemUuid, animatedDye);
SkyblockerConfigManager.save();
SkyblockerConfigManager.update(config -> config.general.customAnimatedDyes.put(itemUuid, animatedDye));
source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customAnimatedDyes.added")));
}
} else {
Expand All @@ -91,18 +94,31 @@ private static int customizeAnimatedDye(FabricClientCommandSource source, int co
}

public static int animateColorTransition(AnimatedDye animatedDye) {
AnimatedDyeStateTracker trackedState = STATE_TRACKER_MAP.computeIfAbsent(animatedDye, NEW_STATE_TRACKER);
AnimatedDyeStateTracker trackedState = STATE_TRACKER_MAP.computeIfAbsent(animatedDye, CustomArmorAnimatedDyes::createStateTracker);

if (trackedState.lastRecordedTick + animatedDye.tickDelay() > ticks) {
if (trackedState.lastRecordedFrame == frames) {
return trackedState.lastColor;
}

trackedState.lastRecordedTick = ticks;
trackedState.lastRecordedFrame = frames;

return animatedDye.interpolate(trackedState);
return animatedDye.interpolate(trackedState, MinecraftClient.getInstance().getRenderTickCounter());
}

private static class AnimatedDyeStateTracker {
private static AnimatedDyeStateTracker createStateTracker(AnimatedDye animatedDye) {
AnimatedDyeStateTracker tracker = new AnimatedDyeStateTracker();
if (animatedDye.delay() > 0) {
if (animatedDye.cycleBack()) {
tracker.onBackCycle = true;
tracker.progress = Math.clamp(animatedDye.delay() / animatedDye.duration(), 0, 1);
} else {
tracker.progress = Math.clamp(1 - animatedDye.delay() / animatedDye.duration(), 0, 1);
}
}
return tracker;
}

private static class AnimatedDyeStateTrackerOld {
private int sampleCounter;
private boolean onBackCycle = false;
private int lastColor = 0;
Expand All @@ -120,14 +136,64 @@ int getAndIncrement() {
return sampleCounter++;
}

static AnimatedDyeStateTracker create() {
return new AnimatedDyeStateTracker();
static AnimatedDyeStateTrackerOld create() {
return new AnimatedDyeStateTrackerOld();
}
}

private static class AnimatedDyeStateTracker {
private float progress = 0;
private boolean onBackCycle = false;
private int lastColor = 0;
private int lastRecordedFrame = 0;
}

public static void cleanTrackers() {
STATE_TRACKER_MAP.clear();
}

public record DyeFrame(@SerialEntry int color, @SerialEntry float time) {}
public record AnimatedDye(@SerialEntry List<DyeFrame> frames, @SerialEntry boolean cycleBack, @SerialEntry float delay, @SerialEntry float duration) {

private int interpolate(AnimatedDyeStateTracker tracker, RenderTickCounter counter) {

int dyeFrame = 0;
while (dyeFrame < frames.size() - 1 && frames.get(dyeFrame + 1).time < tracker.progress) dyeFrame++;


DyeFrame current = tracker.onBackCycle ? frames.get(dyeFrame + 1) : frames.get(dyeFrame);
DyeFrame next = tracker.onBackCycle ? frames.get(dyeFrame) : frames.get(dyeFrame + 1);

float progress = (tracker.progress - current.time) / (next.time - current.time);

tracker.lastColor = OkLabColor.interpolate(current.color, next.color, progress);

float v = counter.getDynamicDeltaTicks() * 0.05f / duration();
if (tracker.onBackCycle) {
tracker.progress -= v;
if (tracker.progress <= 0f) {
tracker.onBackCycle = false;
tracker.progress = Math.abs(tracker.progress);
}
} else {
tracker.progress += v;
if (tracker.progress >= 1f) {
if (cycleBack) {
tracker.onBackCycle = true;
tracker.progress = 2f - tracker.progress;
} else {
tracker.progress %= 1.f;
}
}
}
return tracker.lastColor;
}
}

public record AnimatedDye(@SerialEntry int color1, @SerialEntry int color2, @SerialEntry int samples, @SerialEntry boolean cycleBack, @SerialEntry int tickDelay) {

private int interpolate(AnimatedDyeStateTracker stateTracker) {
public record AnimatedDyeOld(@SerialEntry int color1, @SerialEntry int color2, @SerialEntry int samples, @SerialEntry boolean cycleBack, @SerialEntry int tickDelay) {

private int interpolate(AnimatedDyeStateTrackerOld stateTracker) {
if (stateTracker.shouldCycleBack(samples, cycleBack)) stateTracker.onBackCycle = true;

if (stateTracker.onBackCycle) {
Expand All @@ -152,5 +218,6 @@ private int interpolate(AnimatedDyeStateTracker stateTracker) {

return interpolatedColor;
}

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.hysky.skyblocker.skyblock.item;
package de.hysky.skyblocker.skyblock.item.custom;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
Expand Down Expand Up @@ -45,15 +45,13 @@ private static int customizeDyeColor(FabricClientCommandSource source, int color

if (color == Integer.MIN_VALUE) {
if (customDyeColors.containsKey(itemUuid)) {
customDyeColors.removeInt(itemUuid);
SkyblockerConfigManager.save();
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 {
customDyeColors.put(itemUuid, color);
SkyblockerConfigManager.save();
SkyblockerConfigManager.update(config-> config.general.customDyeColors.put(itemUuid, color));
source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customDyeColors.added")));
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.hysky.skyblocker.skyblock.item;
package de.hysky.skyblocker.skyblock.item.custom;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
Expand All @@ -18,7 +18,6 @@
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;
Expand Down Expand Up @@ -49,13 +48,12 @@ public static void init() {

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);
RegistryWrapper.WrapperLookup wrapperLookup = Utils.getWrapperLookup();
for (Reference<ArmorTrimMaterial> material : wrapperLookup.getOrThrow(RegistryKeys.TRIM_MATERIAL).streamEntries().toList()) {
for (Reference<ArmorTrimPattern> pattern : wrapperLookup.getOrThrow(RegistryKeys.TRIM_PATTERN).streamEntries().toList()) {
ArmorTrim trim = new ArmorTrim(material, pattern);
Expand All @@ -71,10 +69,6 @@ private static void initializeTrimCache() {
}
}

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<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
dispatcher.register(ClientCommandManager.literal("skyblocker")
.then(ClientCommandManager.literal("custom")
Expand Down Expand Up @@ -106,8 +100,7 @@ private static int customizeTrim(FabricClientCommandSource source, Identifier ma

if (material == null && pattern == null) {
if (customArmorTrims.containsKey(itemUuid)) {
customArmorTrims.remove(itemUuid);
SkyblockerConfigManager.save();
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")));
Expand All @@ -121,8 +114,7 @@ private static int customizeTrim(FabricClientCommandSource source, Identifier ma
return Command.SINGLE_SUCCESS;
}

customArmorTrims.put(itemUuid, trimId);
SkyblockerConfigManager.save();
SkyblockerConfigManager.update(config -> config.general.customArmorTrims.put(itemUuid, trimId));
source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.customArmorTrims.added")));
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.hysky.skyblocker.skyblock.item;
package de.hysky.skyblocker.skyblock.item.custom;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
Expand Down
Loading
Loading