structure and player-dependent capes

This commit is contained in:
2025-02-27 16:06:17 +01:00
parent 2a13d44fee
commit f5009f70e0
10 changed files with 329 additions and 164 deletions
@@ -1,20 +1,24 @@
package org.auraclient.auraclient; package org.auraclient.auraclient;
import org.auraclient.auraclient.cloaks.Cloaks;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import org.auraclient.auraclient.cloaks.Cloaks;
import org.auraclient.auraclient.event.KeyInputHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/* /**
* Massive thank you to IIpho3nix for making this mod, i (leo) have modified it to fit Aura Client * Main class for the Aura Client mod.
*/ * Originally created by IIpho3nix and modified for Aura Client by leo.
*/
public class AuraClient implements ModInitializer { public class AuraClient implements ModInitializer {
public static final String MOD_ID = "auraclient"; public static final String MOD_ID = "auraclient";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override @Override
public void onInitialize() { public void onInitialize() {
LOGGER.info("Initializing " + MOD_ID);
KeyInputHandler.register();
Cloaks.initialize(); Cloaks.initialize();
LOGGER.info(MOD_ID + " initialization complete");
} }
} }
@@ -5,25 +5,49 @@ import net.minecraft.client.gui.Drawable;
import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderLayer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
/**
* Represents a drawable texture in the Aura Client UI.
* Uses the builder pattern for flexible texture creation and configuration.
*/
public class AuraClientTexture implements Drawable { public class AuraClientTexture implements Drawable {
// Textures // Predefined textures
public static Identifier BOX_1_1 = Identifier.of("auraclient", "textures/gui/box_1_1.png"); public static final Identifier BOX_1_1 = Identifier.of("auraclient", "textures/gui/box_1_1.png");
Identifier sprite; private final Identifier sprite;
int x = 0; private int x;
int y = 0; private int y;
int width = 0; private int width;
int height = 0; private int height;
boolean focused = false;
public AuraClientTexture(Identifier sprite) { /**
* Creates a new texture with the specified sprite.
* @param sprite The identifier for the texture sprite
*/
private AuraClientTexture(Identifier sprite) {
this.sprite = sprite; this.sprite = sprite;
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
} }
/**
* Creates a new builder instance for the texture.
* @param sprite The identifier for the texture sprite
* @return A new AuraClientTexture instance
*/
public static AuraClientTexture builder(Identifier sprite) { public static AuraClientTexture builder(Identifier sprite) {
return new AuraClientTexture(sprite); return new AuraClientTexture(sprite);
} }
/**
* Sets the dimensions and position of the texture.
* @param x The x coordinate
* @param y The y coordinate
* @param width The width of the texture
* @param height The height of the texture
* @return This instance for method chaining
*/
public AuraClientTexture dimensions(int x, int y, int width, int height) { public AuraClientTexture dimensions(int x, int y, int width, int height) {
this.x = x; this.x = x;
this.y = y; this.y = y;
@@ -32,6 +56,15 @@ public class AuraClientTexture implements Drawable {
return this; return this;
} }
/**
* Sets the focused state of the texture.
* @param focused The focused state
* @return This instance for method chaining
*/
public AuraClientTexture setFocused(boolean focused) {
return this;
}
@Override @Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) { public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.drawTexture(RenderLayer::getGuiTextured, sprite, x, y, 0, 0, width, height, width, height); context.drawTexture(RenderLayer::getGuiTextured, sprite, x, y, 0, 0, width, height, width, height);
@@ -1,118 +1,154 @@
package org.auraclient.auraclient.cloaks; package org.auraclient.auraclient.cloaks;
import at.dhyan.open_imaging.GifDecoder; import at.dhyan.open_imaging.GifDecoder;
import net.minecraft.util.Identifier;
import org.auraclient.auraclient.AuraClient; import org.auraclient.auraclient.AuraClient;
import org.auraclient.auraclient.cloaks.utils.AnimatedCapeData; import org.auraclient.auraclient.cloaks.utils.AnimatedCapeData;
import org.auraclient.auraclient.cloaks.utils.IdentifierUtils; import org.auraclient.auraclient.cloaks.utils.IdentifierUtils;
import org.auraclient.auraclient.cloaks.utils.RandomUtils; import org.auraclient.auraclient.cloaks.utils.RandomUtils;
import org.auraclient.auraclient.event.KeyInputHandler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.io.InputStream;
import java.util.List; import java.util.*;
import java.util.Locale;
import java.util.Objects;
/**
* Manages the cloak/cape system for Aura Client.
* Handles loading, caching, and animating player capes.
*/
public class Cloaks { public class Cloaks {
private static final File capesFolder = new File(MinecraftClient.getInstance().runDirectory, "Capes"); private static final String CAPES_RESOURCE_PATH = "assets/auraclient/textures/capes/";
public static final List<String> list = new ArrayList<>(); private static final List<AnimatedCapeData> animatedCapeFrames = new ArrayList<>();
private static final List<AnimatedCapeData> anicape = new ArrayList<>(); private static int currentFrameIndex = 0;
private static int capeindex = 0; private static long lastFrameTime = System.currentTimeMillis();
private static long mili = System.currentTimeMillis(); private static String lastCachedCape = "NoCache";
private static String lastCache = "NotCached";
public static final List<String> availableCapes = new ArrayList<>();
public static Identifier capeCacheIdentifier = null; public static Identifier capeCacheIdentifier = null;
public static String cape = ""; public static final Map<String, String> playerCapes = new HashMap<>();
public static void initialize() { /**
list.clear(); * Initializes the cloak system.
list.add(""); * Loads cape textures from resources.
if (capesFolder.mkdir()) { */
AuraClient.LOGGER.info("New Cape Folder Created"); public static void initialize() {
downloadCloakTextures(); availableCapes.clear();
} else { availableCapes.add("");
AuraClient.LOGGER.info("Cape Folder Already Exists"); loadCloakTextures();
} }
loadCloakTextures();
KeyInputHandler.register();
}
public static void downloadCloakTextures() {
// Todo: Download the cloak textures and put them in the Capes folder
}
private static void loadCloakTextures() {
if (capesFolder.isDirectory()) {
for (final File fileEntry : Objects.requireNonNull(capesFolder.listFiles())) {
if (
fileEntry.getName().endsWith(".png") || fileEntry.getName().endsWith(".gif")
) {
list.add(fileEntry.getName());
}
}
}
}
public static void tick() {
if (!lastCache.equals(cape)) {
if (!cape.isEmpty()) {
if (cape.endsWith(".gif")) {
try {
anicape.clear();
Identifier frameIdentifier;
File file = new File(capesFolder, cape);
final FileInputStream data = new FileInputStream(file);
final GifDecoder.GifImage gif = GifDecoder.read(data);
final int frameCount = gif.getFrameCount();
for (int i = 0; i < frameCount; i++) {
final BufferedImage img = gif.getFrame(i);
final int delay = gif.getDelay(i);
frameIdentifier = Identifier.of(AuraClient.MOD_ID, "capes_" + RandomUtils.randomStringLowercase(10));
IdentifierUtils.registerBufferedImageTexture(frameIdentifier, img);
anicape.add(new AnimatedCapeData(frameIdentifier, delay));
}
capeindex = 0;
capeCacheIdentifier = anicape.get(0).getId();
lastCache = cape;
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
BufferedImage img = null;
File file = new File(capesFolder, cape);
try {
img = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
capeCacheIdentifier = Identifier.of(AuraClient.MOD_ID, "capes_" + cape.toLowerCase(Locale.ROOT).replace(' ', '_').replaceAll("[^a-z0-9/._-]", "")); /**
IdentifierUtils.registerBufferedImageTexture(capeCacheIdentifier, img); * Loads cloak textures from the resources.
lastCache = cape; * Supports both static (.png) and animated (.gif) capes.
} */
} else { private static void loadCloakTextures() {
capeCacheIdentifier = null; // In a resource-based system, we would need to manually list available capes
lastCache = cape; // For now, we can add default capes that we know exist in resources
} // This would need to be updated when adding new capes to resources
} else { availableCapes.add("glitch.png");
if (cape.endsWith(".gif")) { }
if (capeindex > (anicape.size() - 1)) {
capeindex = 0; /**
} * Updates the cape animation state.
if (System.currentTimeMillis() > mili + anicape.get(capeindex).getDelay()) { * Called every tick to handle animated capes.
mili = System.currentTimeMillis(); */
capeCacheIdentifier = anicape.get(capeindex).getId(); public static void tick() {
capeindex++; String currentCape = playerCapes.values().stream().findFirst().orElse("");
}
} if (!lastCachedCape.equals(currentCape)) {
handleCapeChange(currentCape);
} else if (currentCape.endsWith(".gif")) {
updateAnimatedCape();
} }
} }
}
/**
* Handles loading and caching of a new cape texture.
* @param capeName Name of the cape file to load
*/
private static void handleCapeChange(String capeName) {
if (!capeName.isEmpty()) {
if (capeName.endsWith(".gif")) {
loadAnimatedCape(capeName);
} else {
loadStaticCape(capeName);
}
lastCachedCape = capeName;
}
}
/**
* Loads and processes an animated cape from a GIF file in resources.
* @param fileName Name of the GIF file to load
*/
private static void loadAnimatedCape(String fileName) {
try {
animatedCapeFrames.clear();
String resourcePath = CAPES_RESOURCE_PATH + fileName;
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
if (inputStream != null) {
GifDecoder.GifImage gif = GifDecoder.read(inputStream);
int frameCount = gif.getFrameCount();
for (int i = 0; i < frameCount; i++) {
BufferedImage frame = gif.getFrame(i);
int delay = gif.getDelay(i);
Identifier frameId = Identifier.of(AuraClient.MOD_ID,
"capes_" + RandomUtils.randomStringLowercase(10));
IdentifierUtils.registerBufferedImageTexture(frameId, frame);
animatedCapeFrames.add(new AnimatedCapeData(frameId, delay));
}
currentFrameIndex = 0;
capeCacheIdentifier = animatedCapeFrames.get(0).getTextureId();
}
} catch (IOException e) {
AuraClient.LOGGER.error("Failed to load animated cape from resources: " + fileName, e);
}
}
/**
* Loads and processes a static cape from a PNG file in resources.
* @param fileName Name of the PNG file to load
*/
private static void loadStaticCape(String fileName) {
try {
String resourcePath = CAPES_RESOURCE_PATH + fileName;
InputStream inputStream = Cloaks.class.getClassLoader().getResourceAsStream(resourcePath);
if (inputStream != null) {
BufferedImage image = ImageIO.read(inputStream);
String safeFileName = fileName.toLowerCase(Locale.ROOT)
.replace(' ', '_')
.replaceAll("[^a-z0-9/._-]", "");
capeCacheIdentifier = Identifier.of(AuraClient.MOD_ID, "capes_" + safeFileName);
IdentifierUtils.registerBufferedImageTexture(capeCacheIdentifier, image);
}
} catch (IOException e) {
AuraClient.LOGGER.error("Failed to load static cape from resources: " + fileName, e);
}
}
/**
* Updates the current frame of an animated cape.
*/
private static void updateAnimatedCape() {
if (currentFrameIndex >= animatedCapeFrames.size()) {
currentFrameIndex = 0;
}
AnimatedCapeData currentFrame = animatedCapeFrames.get(currentFrameIndex);
if (System.currentTimeMillis() > lastFrameTime + currentFrame.getDelayMs()) {
lastFrameTime = System.currentTimeMillis();
capeCacheIdentifier = currentFrame.getTextureId();
currentFrameIndex++;
}
}
}
@@ -2,20 +2,34 @@ package org.auraclient.auraclient.cloaks.utils;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
/**
* Represents a single frame of an animated cape.
*/
public class AnimatedCapeData { public class AnimatedCapeData {
private final Identifier id; private final Identifier textureId;
private final int delay; private final int delayMs;
public AnimatedCapeData(Identifier id, int delay) { /**
this.id = id; * Creates a new animated cape frame.
this.delay = delay; * @param textureId The texture identifier for this frame
* @param delayMs The delay in milliseconds before the next frame
*/
public AnimatedCapeData(Identifier textureId, int delayMs) {
this.textureId = textureId;
this.delayMs = delayMs;
} }
public Identifier getId() { /**
return id; * @return The texture identifier for this frame
*/
public Identifier getTextureId() {
return textureId;
} }
public int getDelay() { /**
return delay; * @return The delay in milliseconds before the next frame
*/
public int getDelayMs() {
return delayMs;
} }
} }
@@ -2,28 +2,47 @@ package org.auraclient.auraclient.cloaks.utils;
import java.util.Random; import java.util.Random;
public class RandomUtils { /**
* Utility class for generating random strings and numbers.
*/
public final class RandomUtils {
private static final String ALPHANUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final String ALPHANUMERIC_LOWERCASE = "0123456789abcdefghijklmnopqrstuvwxyz";
private static final Random RANDOM = new Random();
private RandomUtils() {
// Prevent instantiation
}
/**
* Generates a random string of specified length using given characters.
*/
private static String random(final int length, final char[] chars) { private static String random(final int length, final char[] chars) {
final StringBuilder stringBuilder = new StringBuilder(); StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++) {
stringBuilder.append(chars[new Random().nextInt(chars.length)]); sb.append(chars[RANDOM.nextInt(chars.length)]);
return stringBuilder.toString(); }
} return sb.toString();
private static String random(final int length, final String chars) {
return random(length, chars.toCharArray());
} }
/**
* Generates a random string of specified length using alphanumeric characters.
*/
public static String randomString(final int length) { public static String randomString(final int length) {
return random(length, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); return random(length, ALPHANUMERIC.toCharArray());
} }
/**
* Generates a random lowercase string of specified length.
*/
public static String randomStringLowercase(final int length) { public static String randomStringLowercase(final int length) {
return random(length, "0123456789abcdefghijklmnopqrstuvwxyz"); return random(length, ALPHANUMERIC_LOWERCASE.toCharArray());
} }
/**
* Generates a random integer between min and max (inclusive).
*/
public static int randBetween(int min, int max) { public static int randBetween(int min, int max) {
Random r = new Random(); return RANDOM.nextInt(max - min + 1) + min;
return r.nextInt(max - min) + min;
} }
} }
@@ -7,18 +7,33 @@ import net.minecraft.client.util.InputUtil;
import org.auraclient.auraclient.menus.MainMenu; import org.auraclient.auraclient.menus.MainMenu;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
/**
* Handles keyboard input events for the Aura Client.
* Manages keybindings and their associated actions.
*/
public class KeyInputHandler { public class KeyInputHandler {
public static final String KEY_CATEGORY_CAPES = "key.category.auraclient"; private static final String KEY_CATEGORY = "key.category.auraclient";
public static final String KEY_MAIN_MENU = "key.auraclient.main_menu"; private static final String MAIN_MENU_KEY_ID = "key.auraclient.main_menu";
public static KeyBinding mainMenuKey; private static final int DEFAULT_MENU_KEY = GLFW.GLFW_KEY_RIGHT_SHIFT;
private static KeyBinding mainMenuKeyBinding;
/**
* Registers all keybindings and their associated event handlers.
* Should be called during mod initialization.
*/
public static void register() { public static void register() {
mainMenuKey = KeyBindingHelper.registerKeyBinding(new KeyBinding( // Register the main menu key binding
KEY_MAIN_MENU, InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_RIGHT_SHIFT, KEY_CATEGORY_CAPES mainMenuKeyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
MAIN_MENU_KEY_ID,
InputUtil.Type.KEYSYM,
DEFAULT_MENU_KEY,
KEY_CATEGORY
)); ));
// Register the event handler for the main menu key
ClientTickEvents.END_CLIENT_TICK.register(client -> { ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (mainMenuKey.wasPressed()) { if (mainMenuKeyBinding.wasPressed()) {
client.setScreen(new MainMenu()); client.setScreen(new MainMenu());
} }
}); });
@@ -1,23 +1,43 @@
package org.auraclient.auraclient.menus; package org.auraclient.auraclient.menus;
import org.auraclient.auraclient.AuraClient;
import org.auraclient.auraclient.cloaks.Cloaks; import org.auraclient.auraclient.cloaks.Cloaks;
import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text; import net.minecraft.text.Text;
public class CloakSelector extends Screen { public class CloakSelector extends Screen {
private TextFieldWidget playerNameField;
public CloakSelector() { public CloakSelector() {
super(Text.literal("Cloaks")); super(Text.literal("Cloaks"));
} }
@Override @Override
protected void init() { protected void init() {
int y = ((this.height - (35 * (Cloaks.list.size()))) / 2); // Add player name text field at the top
this.playerNameField = new TextFieldWidget(this.textRenderer,
(this.width - 200) / 2, 20, 200, 20, Text.literal("Player Name"));
this.playerNameField.setMaxLength(16);
addDrawableChild(this.playerNameField);
for (String cloak : Cloaks.list) { // Calculate starting Y position for cloak buttons, accounting for text field
int y = ((this.height - (35 * (Cloaks.availableCapes.size()))) / 2) + 30;
for (String cloak : Cloaks.availableCapes) {
addDrawableChild(ButtonWidget.builder(Text.literal(cloak), (__) -> { addDrawableChild(ButtonWidget.builder(Text.literal(cloak), (__) -> {
Cloaks.cape = cloak; String playerName = this.playerNameField.getText();
if (!playerName.isEmpty() && !playerName.equals("Player Name")) {
if (cloak.isEmpty()) {
Cloaks.playerCapes.remove(playerName);
AuraClient.LOGGER.info("Cape removed for player: " + playerName);
} else {
Cloaks.playerCapes.put(playerName, cloak);
AuraClient.LOGGER.info("Cape set for player: " + playerName + " to " + cloak);
}
}
}).dimensions((this.width - 50) / 2, y, 50, 30).build()); }).dimensions((this.width - 50) / 2, y, 50, 30).build());
y += 35; y += 35;
@@ -12,7 +12,9 @@ public class MainMenu extends Screen {
@Override @Override
protected void init() { protected void init() {
addDrawableChild(ButtonWidget.builder(Text.literal("Cloaks"), (_button) -> { addDrawableChild(ButtonWidget.builder(Text.literal("Cloaks"), (_button) -> {
client.setScreen(new CloakSelector()); if (client.player != null) {
client.setScreen(new CloakSelector());
}
}).dimensions((this.width - 50) / 2, (this.height - 30) / 2, 50, 30).build()); }).dimensions((this.width - 50) / 2, (this.height - 30) / 2, 50, 30).build());
} }
} }
@@ -1,35 +1,46 @@
package org.auraclient.auraclient.mixin; package org.auraclient.auraclient.mixin;
import org.auraclient.auraclient.AuraClient;
import org.auraclient.auraclient.cloaks.Cloaks;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.network.AbstractClientPlayerEntity; import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.util.SkinTextures; import net.minecraft.client.util.SkinTextures;
import org.auraclient.auraclient.cloaks.Cloaks;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Mixin for handling custom cloaks in the AbstractClientPlayerEntity class.
* Injects custom cape textures for players who have them assigned.
*/
@Mixin(AbstractClientPlayerEntity.class) @Mixin(AbstractClientPlayerEntity.class)
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public class AbstractClientPlayerEntityMixin { public class AbstractClientPlayerEntityMixin {
@Inject(at = @At("RETURN"), method = "getSkinTextures", cancellable = true)
private void getSkinTextures(CallbackInfoReturnable<SkinTextures> cir) {
// AbstractClientPlayerEntity player = (AbstractClientPlayerEntity) (Object) this;
if (((AbstractClientPlayerEntity) (Object) this) instanceof ClientPlayerEntity && Cloaks.capeCacheIdentifier != null) { /**
// AuraClient.LOGGER.info("Rendering skin for: " + player.getUuid()); * Injects custom cape textures into the player's skin textures.
* @param cir Callback containing the original skin textures
*/
@Inject(
method = "getSkinTextures",
at = @At("RETURN"),
cancellable = true
)
private void getSkinTextures(CallbackInfoReturnable<SkinTextures> cir) {
AbstractClientPlayerEntity player = (AbstractClientPlayerEntity) (Object) this;
if (Cloaks.playerCapes.containsKey(player.getName().getString()) &&
Cloaks.capeCacheIdentifier != null) {
SkinTextures textures = cir.getReturnValue(); SkinTextures textures = cir.getReturnValue();
cir.setReturnValue(new SkinTextures( cir.setReturnValue(new SkinTextures(
textures.texture(), textures.texture(),
textures.textureUrl(), textures.textureUrl(),
Cloaks.capeCacheIdentifier, Cloaks.capeCacheIdentifier,
textures.elytraTexture(), textures.elytraTexture(),
textures.model(), textures.model(),
textures.secure() textures.secure()
)); ));
} }
} }
@@ -1,19 +1,30 @@
package org.auraclient.auraclient.mixin; package org.auraclient.auraclient.mixin;
import org.auraclient.auraclient.cloaks.Cloaks;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import org.auraclient.auraclient.cloaks.Cloaks;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Mixin for the MinecraftClient class.
* Handles the periodic updating of cloaks system.
*/
@Mixin(MinecraftClient.class) @Mixin(MinecraftClient.class)
@Environment(EnvType.CLIENT) @Environment(EnvType.CLIENT)
public class MinecraftClientMixin { public class MinecraftClientMixin {
@Inject(at = @At("HEAD"), method = "tick")
/**
* Injects into the client tick to update cloaks.
* @param ci Callback info
*/
@Inject(
method = "tick",
at = @At("HEAD")
)
private void tick(CallbackInfo ci) { private void tick(CallbackInfo ci) {
Cloaks.tick(); Cloaks.tick();
} }