package ru.zenith.implement.features.draggables;
import dev.redstones.mediaplayerinfo.IMediaSession;
import dev.redstones.mediaplayerinfo.MediaInfo;
import dev.redstones.mediaplayerinfo.MediaPlayerInfo;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.hud.ClientBossBar;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.NativeImageBackedTexture;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import ru.zenith.api.feature.draggable.AbstractDraggable;
import ru.zenith.api.system.animation.Animation;
import ru.zenith.api.system.animation.Direction;
import ru.zenith.api.system.animation.implement.DecelerateAnimation;
import ru.zenith.api.system.font.FontRenderer;
import ru.zenith.api.system.font.Fonts;
import ru.zenith.api.system.shape.ShapeProperties;
import ru.zenith.common.QuickImports;
import ru.zenith.common.util.color.ColorUtil;
import ru.zenith.common.util.math.MathUtil;
import ru.zenith.common.util.render.Render2DUtil;
import ru.zenith.common.util.render.ScissorManager;
import ru.zenith.common.util.world.ServerUtil;
import ru.zenith.common.util.other.Instance;
import ru.zenith.core.Main;
import ru.zenith.implement.features.modules.render.Hud;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DynamicIsland extends AbstractDraggable implements QuickImports {
private final Animation internetAnimation = new DecelerateAnimation().setMs(300).setValue(1);
private final Animation mediaAnimation = new DecelerateAnimation().setMs(300).setValue(1);
private final Animation pvpAnimation = new DecelerateAnimation().setMs(300).setValue(1);
private final Animation barAnimation = new DecelerateAnimation().setMs(300).setValue(1);
private final Animation moduleAnimation = new DecelerateAnimation().setMs(300).setValue(1);
private float[] targetBarHeights = new float[]{10f, 8f, 6f};
private float[] currentBarHeights = new float[]{10f, 8f, 6f};
private long lastUpdateTime = 0;
private String currentModuleNotification = "";
private String currentModuleNotificationClean = "";
private long moduleNotificationTime = 0;
private final long MODULE_NOTIFICATION_DURATION = 2000;
private static final Pattern PVP_TIMER_PATTERN = Pattern.compile("(\\d+)");
private String trackName = null;
private String artistsText = null;
private float progress = 0.0f;
private long currentTime = 0;
private long totalTime = 1;
private boolean isPlaying = false;
private IMediaSession activeSession = null;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private final AtomicBoolean polling = new AtomicBoolean(false);
private volatile long lastPollMs = 0L;
private final Identifier coverTextureLocation = Identifier.of("zenith", "music_cover_di");
private NativeImageBackedTexture coverTexture = null;
private int coverHash = 0;
public static DynamicIsland getInstance() {
return Instance.getDraggable(DynamicIsland.class);
}
public DynamicIsland() {
super("Dynamic Island", 0, 4, 100, 18, false);
}
@Override
public void tick() {
if (fullNullCheck()) return;
long now = System.currentTimeMillis();
if (now - lastPollMs < 200L) {
updateAnimationsAndWidth();
return;
}
lastPollMs = now;
if (!polling.compareAndSet(false, true)) {
updateAnimationsAndWidth();
return;
}
executor.execute(() -> {
try {
IMediaSession session = MediaPlayerInfo.Instance.getMediaSessions().stream()
.max(Comparator.comparing(s -> s.getMedia().getPlaying()))
.orElse(null);
if (session != null) {
MediaInfo info = session.getMedia();
if (info != null && !info.getTitle().isEmpty()) {
String newTrackName = info.getTitle();
String newArtistsText = (info.getArtist() != null && !info.getArtist().isEmpty())
? info.getArtist()
: null;
long newCurrentTime = Math.max(0L, info.getPosition());
long newTotalTime = info.getDuration() > 0 ? info.getDuration() : 1;
boolean newPlaying = info.getPlaying();
byte[] newCover = info.getArtworkPng();
int newCoverHash = 0;
NativeImage decodedImage = null;
if (newCover != null && newCover.length > 0) {
try {
newCoverHash = Arrays.hashCode(newCover);
decodedImage = NativeImage.read(new ByteArrayInputStream(newCover));
} catch (Exception ignored) {
decodedImage = null;
newCoverHash = 0;
}
}
NativeImage finalDecodedImage = decodedImage;
int finalCoverHash = newCoverHash;
mc.execute(() -> {
activeSession = session;
trackName = newTrackName;
artistsText = newArtistsText;
totalTime = newTotalTime;
currentTime = newCurrentTime;
progress = (float) newCurrentTime / (float) newTotalTime;
isPlaying = newPlaying;
if (newCover == null || newCover.length == 0) {
clearCoverTexture();
coverHash = 0;
} else {
if (finalDecodedImage != null) {
if (finalCoverHash != coverHash) {
updateCoverTexture(finalDecodedImage);
coverHash = finalCoverHash;
} else {
try {
finalDecodedImage.close();
} catch (Exception ignored) {
}
}
} else {
clearCoverTexture();
coverHash = 0;
}
}
});
} else {
mc.execute(this::clearData);
}
} else {
mc.execute(this::clearData);
}
} catch (Throwable e) {
mc.execute(this::clearData);
} finally {
polling.set(false);
}
});
updateAnimationsAndWidth();
}
private void updateAnimationsAndWidth() {
int ping = 0;
if (mc.player != null && mc.getNetworkHandler() != null && mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid()) != null) {
ping = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid()).getLatency();
}
boolean isPvp = ServerUtil.isPvp();
boolean mediaNull = (trackName == null || trackName.isEmpty());
boolean hasActiveMusic = !mediaNull && !isPvp;
internetAnimation.setDirection(ping < 1000 ? Direction.FORWARDS : Direction.BACKWARDS);
if (isPvp) {
mediaAnimation.setDirection(Direction.BACKWARDS);
} else {
mediaAnimation.setDirection(mediaNull ? Direction.BACKWARDS : Direction.FORWARDS);
}
pvpAnimation.setDirection(isPvp ? Direction.FORWARDS : Direction.BACKWARDS);
barAnimation.setDirection(hasActiveMusic ? Direction.FORWARDS : Direction.BACKWARDS);
if (hasActiveMusic && System.currentTimeMillis() - lastUpdateTime > 100) {
updateBarHeights();
lastUpdateTime = System.currentTimeMillis();
}
for (int i = 0; i < 3; i++) {
currentBarHeights[i] = lerp(currentBarHeights[i], targetBarHeights[i], 0.3f);
}
boolean showModuleNotification = !currentModuleNotification.isEmpty() &&
System.currentTimeMillis() - moduleNotificationTime < MODULE_NOTIFICATION_DURATION;
moduleAnimation.setDirection(showModuleNotification ? Direction.FORWARDS : Direction.BACKWARDS);
}
public void showModuleNotification(String moduleName, boolean enabled) {
currentModuleNotification = moduleName + " " + (enabled ? "§aEnabled" : "§cDisabled");
currentModuleNotificationClean = moduleName + " " + (enabled ? "Enabled" : "Disabled");
moduleNotificationTime = System.currentTimeMillis();
moduleAnimation.reset();
}
private float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
private void updateBarHeights() {
for (int i = 0; i < 3; i++) {
targetBarHeights[i] = 4 + (float) Math.random() * 8;
}
}
private void clearData() {
trackName = null;
artistsText = null;
progress = 0.0f;
currentTime = 0;
totalTime = 1;
activeSession = null;
clearCoverTexture();
}
private void clearCoverTexture() {
try {
TextureManager tm = mc.getTextureManager();
if (tm != null) {
tm.destroyTexture(coverTextureLocation);
}
if (coverTexture != null) {
coverTexture.close();
coverTexture = null;
}
} catch (Exception ignored) {
coverTexture = null;
}
coverHash = 0;
}
private void updateCoverTexture(NativeImage nativeImage) {
try {
if (nativeImage != null) {
TextureManager tm = mc.getTextureManager();
if (tm != null) {
tm.destroyTexture(coverTextureLocation);
}
if (coverTexture != null) {
coverTexture.close();
coverTexture = null;
}
coverTexture = new NativeImageBackedTexture(nativeImage);
if (tm != null) {
tm.registerTexture(coverTextureLocation, coverTexture);
}
}
} catch (Exception e) {
clearCoverTexture();
try {
nativeImage.close();
} catch (Exception ignored) {
}
}
}
@Override
public void drawDraggable(DrawContext context) {
if (fullNullCheck()) return;
MatrixStack matrix = context.getMatrices();
ScissorManager scissor = Main.getInstance().getScissorManager();
String name = "Singularity";
String track = trackName != null ? trackName : "";
String artist = artistsText != null ? artistsText : "";
String fullTrack = track + (artist.isEmpty() ? "" : " - " + artist);
String pvp = "PVP";
String pvpTimer = getPvpTimer();
boolean isPvp = ServerUtil.isPvp();
boolean mediaNull = (trackName == null || trackName.isEmpty());
boolean showModuleNotification = !currentModuleNotification.isEmpty() &&
System.currentTimeMillis() - moduleNotificationTime < MODULE_NOTIFICATION_DURATION;
float padding = 2f;
float round = 6f;
FontRenderer font = Fonts.getSize(12, Fonts.Type.BOLD);
float baseWidth;
if (showModuleNotification) {
baseWidth = 15 + font.getStringWidth(currentModuleNotificationClean) + padding * 2;
} else if (isPvp) {
baseWidth = 15 + font.getStringWidth(pvp) + padding * 3;
} else if (!mediaNull) {
baseWidth = 15 + font.getStringWidth(fullTrack) + padding * 2;
} else {
baseWidth = 15 + font.getStringWidth(name) + padding * 2;
}
float baseHeight = 15f;
float width = baseWidth;
float height = baseHeight;
float x = mc.getWindow().getScaledWidth() / 2f - width / 2f;
float bossBarOffset = 0f;
if (mc.inGameHud != null && mc.inGameHud.getBossBarHud() != null) {
int bossBarCount = mc.inGameHud.getBossBarHud().bossBars.size();
if (bossBarCount > 0) {
bossBarOffset =19 ;
}
}
float y = 4f + bossBarOffset;
scissor.push(matrix.peek().getPositionMatrix(), x - 6, y - 6, width + 10, height + 10);
blur.render(ShapeProperties.create(matrix, x - 1, y - 0.5f, width + 2, height + 1).round(round).softness(0.5f).color(ColorUtil.getColor(16, 16, 16, 180)).build());
if (showModuleNotification && moduleAnimation.getOutput().floatValue() > 0.01f) {
float alpha = (float) moduleAnimation.getOutput().floatValue();
Color textColor;
Color dotColor;
if (currentModuleNotification.contains("§a")) {
textColor = new Color(255, 255, 255, (int) (255 * alpha));
dotColor = new Color(55, 255, 55, (int) (255 * alpha));
} else {
textColor = new Color(255, 255, 255, (int) (255 * alpha));
dotColor = new Color(255, 55, 55, (int) (255 * alpha));
}
font.drawString(matrix, currentModuleNotificationClean, x + padding + 12, y - (padding / 2f) + (font.getStringHeight(currentModuleNotificationClean) / 2f), ColorUtil.getColor(textColor.getRed(), textColor.getGreen(), textColor.getBlue(), textColor.getAlpha()));
rectangle.render(ShapeProperties.create(matrix, x + padding, y + padding, height - padding * 2, height - padding * 2).round(4f).color(ColorUtil.getColor(dotColor.getRed(), dotColor.getGreen(), dotColor.getBlue(), dotColor.getAlpha())).build());
} else if (!mediaNull && !isPvp && mediaAnimation.getOutput().floatValue() > 0.01f) {
float animationAlpha = (float) mediaAnimation.getOutput().floatValue();
float coverSize = baseHeight - padding * 2;
float coverY = y + padding;
float coverX = x + padding;
if (coverTexture != null) {
try {
var tex = mc.getTextureManager().getTexture(coverTextureLocation);
if (tex != null) {
Render2DUtil.drawTexture(context, coverTextureLocation, coverX, coverY, coverSize, 4f,
(int) coverSize, (int) coverSize, (int) coverSize,
ColorUtil.getRect(1),
ColorUtil.multAlpha(ColorUtil.WHITE, animationAlpha));
} else {
rectangle.render(ShapeProperties.create(matrix, coverX, coverY, coverSize, coverSize).round(4f).color(ColorUtil.multAlpha(ColorUtil.getColor(50, 50, 50, 140), animationAlpha)).build());
}
} catch (Exception e) {
rectangle.render(ShapeProperties.create(matrix, coverX, coverY, coverSize, coverSize).round(4f).color(ColorUtil.multAlpha(ColorUtil.getColor(50, 50, 50, 140), animationAlpha)).build());
}
} else {
rectangle.render(ShapeProperties.create(matrix, coverX, coverY, coverSize, coverSize).round(4f).color(ColorUtil.multAlpha(ColorUtil.getColor(50, 50, 50, 140), animationAlpha)).build());
}
font.drawString(matrix, fullTrack,
x + baseHeight, y - (padding / 2f) + (font.getStringHeight(fullTrack) / 2f),
ColorUtil.multAlpha(ColorUtil.WHITE, animationAlpha));
} else if (!isPvp && mediaAnimation.getOutput().floatValue() < 0.99f) {
float defaultAlpha = 1f - (float) mediaAnimation.getOutput().floatValue();
rectangle.render(ShapeProperties.create(matrix, x + padding, y + padding, baseHeight - padding * 2, baseHeight - padding * 2).round(4f).color(ColorUtil.multAlpha(ColorUtil.getClientColor(), defaultAlpha)).build());
font.drawString(matrix, name,
x + baseHeight, y - (padding / 2f) + (font.getStringHeight(name) / 2f),
ColorUtil.multAlpha(ColorUtil.WHITE, defaultAlpha));
} else if (isPvp && pvpAnimation.getOutput().floatValue() > 0.01f) {
float pvpAlpha = (float) pvpAnimation.getOutput().floatValue();
rectangle.render(ShapeProperties.create(matrix, x + padding, y + padding, baseHeight + 2f - padding * 2, baseHeight - padding * 2).round(4f).color(ColorUtil.multAlpha(ColorUtil.RED, pvpAlpha)).build());
FontRenderer timerFont = Fonts.getSize(13, Fonts.Type.DEFAULT);
timerFont.drawString(matrix, pvpTimer, x + baseHeight - timerFont.getStringWidth(pvpTimer) / 2 - padding * 3.5f, y - (padding / 2f) + (timerFont.getStringHeight(pvpTimer) / 1.5f), ColorUtil.multAlpha(ColorUtil.WHITE, pvpAlpha));
font.drawString(matrix, pvp, x + baseHeight + padding, y - (padding / 2f) + (font.getStringHeight(pvp) / 2f), ColorUtil.multAlpha(ColorUtil.WHITE, pvpAlpha));
}
scissor.pop();
String currentTime = LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
FontRenderer timeFont = Fonts.getSize(13, Fonts.Type.DEFAULT);
timeFont.drawString(matrix, currentTime, x - 1 - (padding * 3f) - timeFont.getStringWidth(currentTime), y - 0.5f - (padding / 2f) + (timeFont.getStringHeight(currentTime) / 2f), ColorUtil.WHITE);
int ping = 0;
if (mc.player != null && mc.getNetworkHandler() != null && mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid()) != null) {
ping = mc.getNetworkHandler().getPlayerListEntry(mc.player.getUuid()).getLatency();
}
float baseBarY = y + padding + (Fonts.getSize(7, Fonts.Type.ICONS).getStringHeight("P") / 2f) - 4 + 1;
float[] barYs = new float[3];
for (int i = 0; i < 3; i++) {
barYs[i] = baseBarY + (10 - currentBarHeights[i]) / 2f;
}
boolean hasActiveMusic = !mediaNull && !isPvp;
float internetAlpha = (float) internetAnimation.getOutput().floatValue();
float barAlpha = (float) barAnimation.getOutput().floatValue();
if (hasActiveMusic && barAlpha > 0.01f && internetAlpha > 0.01f) {
float combinedAlpha = internetAlpha * barAlpha;
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f), barYs[0], 3.5F, currentBarHeights[0]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 4, barYs[1], 3.5F, currentBarHeights[1]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 8, barYs[2], 3.5F, currentBarHeights[2]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
} else if (internetAlpha > 0.01f) {
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f), baseBarY, 3.5F, 10).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, internetAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 4, baseBarY + 2, 3.5F, 8).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, internetAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 8, baseBarY + 4 - 0.5F, 3.5F, 6.5F).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, internetAlpha)).build());
}
if (internetAlpha < 0.99f) {
float combinedAlpha = internetAlpha * barAlpha;
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f), barYs[0], 3.5F, currentBarHeights[0]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 4, barYs[1], 3.5F, currentBarHeights[1]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
rectangle.render(ShapeProperties.create(matrix, x + width + (padding * 3f) + 8, barYs[2], 3.5F, currentBarHeights[2]).round(1f).color(ColorUtil.multAlpha(ColorUtil.WHITE, combinedAlpha)).build());
}
}
private String getPvpTimer() {
if (mc.inGameHud == null || mc.inGameHud.getBossBarHud() == null) {
return "30";
}
for (ClientBossBar bossBar : mc.inGameHud.getBossBarHud().bossBars.values()) {
String name = bossBar.getName().getString().toLowerCase();
if (name.contains("pvp") || name.contains("пвп")) {
Matcher matcher = PVP_TIMER_PATTERN.matcher(bossBar.getName().getString());
if (matcher.find()) {
return matcher.group(1);
}
}
}
return "30";
}
private boolean fullNullCheck() {
return mc.player == null || mc.world == null;
}
}