package choonster.testmod3.client.cape;
import choonster.testmod3.Logger;
import choonster.testmod3.TestMod3;
import choonster.testmod3.util.ReflectionUtil;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.util.ResourceLocation;
import java.lang.invoke.MethodHandle;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class CapeUtils {
private static final ResourceLocation CAPE_LOCATION = new ResourceLocation(TestMod3.MODID, "textures/capes/testmod3.png");
private static final UUID UUID_CHOONSTER = UUID.fromString("12bbe833-bf2b-4daa-adb0-9a7f6e2f4f38");
// Copied from SkinManager
private static final ExecutorService THREAD_POOL = new ThreadPoolExecutor(0, 2, 1L, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
private static final MethodHandle GET_PLAYER_INFO = ReflectionUtil.findMethod(AbstractClientPlayer.class, "getPlayerInfo", "func_175155_b");
private static final MethodHandle GET_PLAYER_TEXTURES = ReflectionUtil.findFieldGetter(NetworkPlayerInfo.class, "playerTextures", "field_187107_a");
/**
* Queue the replacement of a player's cape with the TestMod3 cape.
* <p>
* In at least 100 milliseconds, the player's cape will be replaced on the next iteration of the client's main loop.
*
* @param player The player
*/
static void queuePlayerCapeReplacement(AbstractClientPlayer player) {
final String displayName = player.getDisplayNameString();
Logger.info("Queueing cape replacement for %s", displayName);
THREAD_POOL.submit(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Logger.fatal(e, "Cape delay thread for %s interrupted", displayName);
return;
}
Minecraft.getMinecraft().addScheduledTask(() -> replacePlayerCape(player));
});
}
/**
* Replace a player's cape with the TestMod3 cape.
*
* @param player The player
*/
@SuppressWarnings("unchecked")
private static void replacePlayerCape(AbstractClientPlayer player) {
final String displayName = player.getDisplayNameString();
final NetworkPlayerInfo playerInfo;
try {
playerInfo = (NetworkPlayerInfo) GET_PLAYER_INFO.invokeExact(player);
} catch (Throwable throwable) {
Logger.fatal(throwable, "Failed to get NetworkPlayerInfo of %s", displayName);
return;
}
if (playerInfo == null) {
Logger.fatal("NetworkPlayerInfo of %s is null", displayName);
return;
}
final Map<MinecraftProfileTexture.Type, ResourceLocation> playerTextures;
try {
playerTextures = (Map<MinecraftProfileTexture.Type, ResourceLocation>) GET_PLAYER_TEXTURES.invokeExact(playerInfo);
} catch (Throwable throwable) {
Logger.fatal(throwable, "Failed to get player textures of %s", displayName);
return;
}
playerTextures.put(MinecraftProfileTexture.Type.CAPE, CAPE_LOCATION);
Logger.info("Replaced cape of %s!", displayName);
}
/**
* Does the player have a TestMod3 cape?
* <p>
* Currently only returns true for me (Choonster)
*
* @param player The player
* @return True if the player has a TestMod3 cape
*/
static boolean doesPlayerHaveCape(AbstractClientPlayer player) {
return player.getUniqueID().equals(UUID_CHOONSTER);
}
}